From c273ae62e9b3296640e6a9b73482dad30cbd6bb0 Mon Sep 17 00:00:00 2001 From: RaenonX Date: Thu, 5 Aug 2021 23:09:25 -0500 Subject: [PATCH] ADD - Affliction icon besides text (#231) Signed-off-by: RaenonX --- .../elements/markdown/main.test.tsx | 101 +++++++++++++++++- .../markdown/transformers/text/icon/const.ts | 1 + .../transformers/text/icon/main.module.css | 5 + .../text/icon/main.module.css.map | 1 + .../transformers/text/icon/main.module.scss | 5 + .../markdown/transformers/text/icon/main.tsx | 42 ++++++++ .../markdown/transformers/text/icon/types.ts | 3 + .../markdown/transformers/text/icon/utils.ts | 9 ++ .../markdown/transformers/text/main.tsx | 15 ++- .../markdown/transformers/text/syntax.ts | 8 ++ .../markdown/transformers/text/utils.test.ts | 72 +++++++++++++ .../markdown/transformers/text/utils.ts | 24 +++++ test/render/main.tsx | 4 +- 13 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 src/components/elements/markdown/transformers/text/icon/const.ts create mode 100644 src/components/elements/markdown/transformers/text/icon/main.module.css create mode 100644 src/components/elements/markdown/transformers/text/icon/main.module.css.map create mode 100644 src/components/elements/markdown/transformers/text/icon/main.module.scss create mode 100644 src/components/elements/markdown/transformers/text/icon/main.tsx create mode 100644 src/components/elements/markdown/transformers/text/icon/types.ts create mode 100644 src/components/elements/markdown/transformers/text/icon/utils.ts create mode 100644 src/components/elements/markdown/transformers/text/utils.test.ts create mode 100644 src/components/elements/markdown/transformers/text/utils.ts diff --git a/src/components/elements/markdown/main.test.tsx b/src/components/elements/markdown/main.test.tsx index 5f0514d7..6995f6c2 100644 --- a/src/components/elements/markdown/main.test.tsx +++ b/src/components/elements/markdown/main.test.tsx @@ -5,8 +5,9 @@ import userEvent from '@testing-library/user-event'; import {renderReact} from '../../../../test/render/main'; import {SupportedLanguages, UnitType} from '../../../api-def/api'; -import {DepotPaths, SimpleUnitInfo} from '../../../api-def/resources'; +import {DepotPaths, SimpleUnitInfo, StatusEnums} from '../../../api-def/resources'; import {Markdown} from './main'; +import {makeAfflictionIconMarkdown} from './transformers/text/icon/utils'; describe('Markdown', () => { @@ -314,4 +315,102 @@ describe('Markdown', () => { expect(await screen.findByText('Analysis')).toBeInTheDocument(); }); }); + + describe('Markdown - Affliction Icon', () => { + const status: StatusEnums['status'] = [ + { + name: 'POISON', + code: 1, + imagePath: 'poison.png', + trans: { + [SupportedLanguages.CHT]: 'Poison CHT', + [SupportedLanguages.EN]: 'Poison', + [SupportedLanguages.JP]: 'Poison JP', + }, + }, + ]; + + it('shows affliction icon in sentence', async () => { + const markdown = 'Afflicts poison'; + + renderReact( + () => {markdown}, + {resources: {afflictions: {status}}}, + ); + + expect(screen.getByText(/Poison/)).toBeInTheDocument(); + expect(screen.getByText('', {selector: 'img'})).toHaveAttribute('src', DepotPaths.getImageURL('poison.png')); + }); + + it('does not show multiple affliction icons for one if already exists', async () => { + const markdown = `${makeAfflictionIconMarkdown(status[0])} poison`; + + renderReact( + () => {markdown}, + {resources: {afflictions: {status}}}, + ); + + expect(screen.getAllByText(/poison/).length).toBe(1); + expect(screen.getAllByText('', {selector: 'img'}).length).toBe(1); + }); + + it('shows multiple affliction icons for different afflictions', async () => { + const markdown = `Afflicts poison, also afflicts poison`; + + renderReact( + () => {markdown}, + {resources: {afflictions: {status}}}, + ); + + expect(screen.getAllByText('', {selector: 'img'}).length).toBe(2); + }); + + it('shows affliction icon in table cell', async () => { + const markdown = `head | col 2\n:---: | :---:\nX | poison`; + + renderReact( + () => {markdown}, + {resources: {afflictions: {status}}}, + ); + + expect(screen.getByText(/Poison/)).toBeInTheDocument(); + expect(screen.getByText('', {selector: 'img'})).toHaveAttribute('src', DepotPaths.getImageURL('poison.png')); + }); + + it('shows affliction icon in text', async () => { + const markdown = `**Afflicts poison**.`; + + renderReact( + () => {markdown}, + {resources: {afflictions: {status}}}, + ); + + expect(screen.getByText(/Poison/)).toBeInTheDocument(); + expect(screen.getByText('', {selector: 'img'})).toHaveAttribute('src', DepotPaths.getImageURL('poison.png')); + }); + + it('shows affliction icon in heading', async () => { + const markdown = `### Afflicts poison`; + + renderReact( + () => {markdown}, + {resources: {afflictions: {status}}}, + ); + + expect(screen.getByText(/Poison/)).toBeInTheDocument(); + expect(screen.getByText('', {selector: 'img'})).toHaveAttribute('src', DepotPaths.getImageURL('poison.png')); + }); + + it('shows affliction icon in colored text', async () => { + const markdown = `::[green] Afflicts poison::`; + + renderReact( + () => {markdown}, + {resources: {afflictions: {status}}}, + ); + + expect(screen.getByText(/Poison/)).toBeInTheDocument(); + expect(screen.getByText('', {selector: 'img'})).toHaveAttribute('src', DepotPaths.getImageURL('poison.png')); + }); + }); }); diff --git a/src/components/elements/markdown/transformers/text/icon/const.ts b/src/components/elements/markdown/transformers/text/icon/const.ts new file mode 100644 index 00000000..6c474014 --- /dev/null +++ b/src/components/elements/markdown/transformers/text/icon/const.ts @@ -0,0 +1 @@ +export const IDENTIFIER_SEPARATOR = '|'; diff --git a/src/components/elements/markdown/transformers/text/icon/main.module.css b/src/components/elements/markdown/transformers/text/icon/main.module.css new file mode 100644 index 00000000..170e4d7c --- /dev/null +++ b/src/components/elements/markdown/transformers/text/icon/main.module.css @@ -0,0 +1,5 @@ +img.afflictionIcon { + margin: 0 0.2rem; +} + +/*# sourceMappingURL=main.module.css.map */ diff --git a/src/components/elements/markdown/transformers/text/icon/main.module.css.map b/src/components/elements/markdown/transformers/text/icon/main.module.css.map new file mode 100644 index 00000000..0a219714 --- /dev/null +++ b/src/components/elements/markdown/transformers/text/icon/main.module.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["main.module.scss"],"names":[],"mappings":"AACE;EACE","file":"main.module.css"} \ No newline at end of file diff --git a/src/components/elements/markdown/transformers/text/icon/main.module.scss b/src/components/elements/markdown/transformers/text/icon/main.module.scss new file mode 100644 index 00000000..a0d59e7d --- /dev/null +++ b/src/components/elements/markdown/transformers/text/icon/main.module.scss @@ -0,0 +1,5 @@ +img { + &.afflictionIcon { + margin: 0 0.2rem; + } +} diff --git a/src/components/elements/markdown/transformers/text/icon/main.tsx b/src/components/elements/markdown/transformers/text/icon/main.tsx new file mode 100644 index 00000000..8073c317 --- /dev/null +++ b/src/components/elements/markdown/transformers/text/icon/main.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +import {DepotPaths} from '../../../../../../api-def/resources'; +import {AppReactContext} from '../../../../../../context/app/main'; +import {useI18n} from '../../../../../../i18n/hook'; +import {Image} from '../../../../common/image'; +import {TextComponentProps} from '../types'; +import {IDENTIFIER_SEPARATOR} from './const'; +import styles from './main.module.css'; +import {IconType} from './types'; + + +export const Icon = ({children}: TextComponentProps) => { + const {lang} = useI18n(); + const context = React.useContext(AppReactContext); + + const [type, identifier] = children.split(IDENTIFIER_SEPARATOR, 2); + + if (type === IconType.AFFLICTION && context?.resources.afflictions.status.length) { + const statusEnum = context?.resources.afflictions.status.find((entry) => entry.name === identifier); + + if (!statusEnum) { + // Identifier invalid / not found + return <>{children}; + } + + if (!statusEnum.imagePath) { + // No image to show + return <>; + } + + return ( + + ); + } + + return <>{children}; +}; diff --git a/src/components/elements/markdown/transformers/text/icon/types.ts b/src/components/elements/markdown/transformers/text/icon/types.ts new file mode 100644 index 00000000..79c69728 --- /dev/null +++ b/src/components/elements/markdown/transformers/text/icon/types.ts @@ -0,0 +1,3 @@ +export const IconType = { + AFFLICTION: 'aff', +}; diff --git a/src/components/elements/markdown/transformers/text/icon/utils.ts b/src/components/elements/markdown/transformers/text/icon/utils.ts new file mode 100644 index 00000000..8c9f9469 --- /dev/null +++ b/src/components/elements/markdown/transformers/text/icon/utils.ts @@ -0,0 +1,9 @@ +import {EnumEntry} from '../../../../../../api-def/resources'; +import {iconSyntax} from '../syntax'; +import {IDENTIFIER_SEPARATOR} from './const'; +import {IconType} from './types'; + + +export const makeAfflictionIconMarkdown = (affliction: EnumEntry) => { + return `${iconSyntax.start}${IconType.AFFLICTION}${IDENTIFIER_SEPARATOR}${affliction.name}${iconSyntax.end}`; +}; diff --git a/src/components/elements/markdown/transformers/text/main.tsx b/src/components/elements/markdown/transformers/text/main.tsx index 1279354c..46cc1d88 100644 --- a/src/components/elements/markdown/transformers/text/main.tsx +++ b/src/components/elements/markdown/transformers/text/main.tsx @@ -1,7 +1,10 @@ import React from 'react'; +import {AppReactContext} from '../../../../../context/app/main'; +import {useI18n} from '../../../../../i18n/hook'; import {syntaxCollection} from './syntax'; import {TextComponentProps} from './types'; +import {injectMarkdownToText} from './utils'; export const Text = ({children}: TextComponentProps) => { @@ -44,6 +47,9 @@ type TextChildrenProps = { } export const TextChildren = ({children}: TextChildrenProps) => { + const {lang} = useI18n(); + const context = React.useContext(AppReactContext); + if (!children) { return <>; } @@ -53,7 +59,14 @@ export const TextChildren = ({children}: TextChildrenProps) => { { children.map((child, idx) => { if (typeof child === 'string') { - return {child}; + const injected = injectMarkdownToText( + lang, child, + { + afflictions: context?.resources.afflictions.status || [], + }, + ); + + return {injected}; } return {child}; diff --git a/src/components/elements/markdown/transformers/text/syntax.ts b/src/components/elements/markdown/transformers/text/syntax.ts index eb1ef0ad..9b90c630 100644 --- a/src/components/elements/markdown/transformers/text/syntax.ts +++ b/src/components/elements/markdown/transformers/text/syntax.ts @@ -1,10 +1,17 @@ import {CalcExpression} from '../math/main'; import {ColoredText} from './color'; import {EnlargedTextLevel2, EnlargedTextLevel3} from './enlarge'; +import {Icon} from './icon/main'; import {Syntax} from './types'; import {MarkdownUnitName} from './unit'; +export const iconSyntax: Syntax = { + start: '{', + end: '}', + Component: Icon, +}; + const colorSyntax: Syntax = { start: '::', end: '::', @@ -37,6 +44,7 @@ const enlargeLv2Syntax: Syntax = { // NOTE: Order matters! export const syntaxCollection = [ + iconSyntax, colorSyntax, calcSyntax, unitSyntax, diff --git a/src/components/elements/markdown/transformers/text/utils.test.ts b/src/components/elements/markdown/transformers/text/utils.test.ts new file mode 100644 index 00000000..a9a4994d --- /dev/null +++ b/src/components/elements/markdown/transformers/text/utils.test.ts @@ -0,0 +1,72 @@ +import {expect} from '@jest/globals'; + +import {SupportedLanguages} from '../../../../../api-def/api'; +import {StatusEnums} from '../../../../../api-def/resources'; +import {makeAfflictionIconMarkdown} from './icon/utils'; +import {injectMarkdownToText} from './utils'; + + +describe('Text injection', () => { + const afflictions: StatusEnums['status'] = [ + { + name: 'POISON', + code: 1, + imagePath: 'poison.png', + trans: { + [SupportedLanguages.CHT]: 'Poison CHT', + [SupportedLanguages.EN]: 'Poison', + [SupportedLanguages.JP]: 'Poison JP', + }, + }, + ]; + + it('does not inject anything', async () => { + const injected = injectMarkdownToText(SupportedLanguages.EN, 'Text', {afflictions}); + + expect(injected).toBe('Text'); + }); + + it('injects affliction icon before affliction text', async () => { + const injected = injectMarkdownToText(SupportedLanguages.EN, 'Poison', {afflictions}); + + expect(injected).toBe(`${makeAfflictionIconMarkdown(afflictions[0])}Poison`); + }); + + it('injects affliction icon in a sentence', async () => { + const injected = injectMarkdownToText(SupportedLanguages.EN, 'Afflicts Poison', {afflictions}); + + expect(injected).toBe(`Afflicts ${makeAfflictionIconMarkdown(afflictions[0])}Poison`); + }); + + it('injects affliction icon in a continuous word', async () => { + const injected = injectMarkdownToText(SupportedLanguages.EN, 'ApplyPoison', {afflictions}); + + expect(injected).toBe(`Apply${makeAfflictionIconMarkdown(afflictions[0])}Poison`); + }); + + it('injects affliction icon case-insensitively', async () => { + const injected = injectMarkdownToText(SupportedLanguages.EN, 'Afflicts poison', {afflictions}); + + expect(injected).toBe(`Afflicts ${makeAfflictionIconMarkdown(afflictions[0])}Poison`); + }); + + it('does not inject affliction icon if already exists', async () => { + const injected = injectMarkdownToText( + SupportedLanguages.EN, + `${makeAfflictionIconMarkdown(afflictions[0])}Poison`, + {afflictions}, + ); + + expect(injected).toBe(`${makeAfflictionIconMarkdown(afflictions[0])}Poison`); + }); + + it('does not inject affliction icon if already exists (precedes with space)', async () => { + const injected = injectMarkdownToText( + SupportedLanguages.EN, + `${makeAfflictionIconMarkdown(afflictions[0])} Poison`, + {afflictions}, + ); + + expect(injected).toBe(`${makeAfflictionIconMarkdown(afflictions[0])} Poison`); + }); +}); diff --git a/src/components/elements/markdown/transformers/text/utils.ts b/src/components/elements/markdown/transformers/text/utils.ts new file mode 100644 index 00000000..3da32bbd --- /dev/null +++ b/src/components/elements/markdown/transformers/text/utils.ts @@ -0,0 +1,24 @@ +import {SupportedLanguages} from '../../../../../api-def/api'; +import {StatusEnums} from '../../../../../api-def/resources'; +import {IDENTIFIER_SEPARATOR} from './icon/const'; +import {makeAfflictionIconMarkdown} from './icon/utils'; +import {iconSyntax} from './syntax'; + + +type Resources = { + afflictions?: StatusEnums['status'], +} + +export const injectMarkdownToText = (lang: SupportedLanguages, text: string, resources: Resources): string => { + // Affliction icons + if (resources.afflictions?.length) { + resources.afflictions.forEach((entry) => { + text = text.replace( + new RegExp(`(^|([^${iconSyntax.end}${IDENTIFIER_SEPARATOR} ]) ?)${entry.trans[lang]}`, 'gi'), + `$1${makeAfflictionIconMarkdown(entry)}${entry.trans[lang]}`, + ); + }); + } + + return text; +}; diff --git a/test/render/main.tsx b/test/render/main.tsx index 28dd8dfb..cee06585 100644 --- a/test/render/main.tsx +++ b/test/render/main.tsx @@ -42,9 +42,7 @@ const RenderWrapper = ({store, options, children}: React.PropsWithChildren, - }, + afflictions: {status: [] as Array}, simpleUnitInfo: {}, }, options?.resources,