From f0d3ef5489ef86c48bdd2f2d67d61e1b8bed11ae Mon Sep 17 00:00:00 2001 From: seferturan Date: Wed, 12 Feb 2025 14:02:06 +0100 Subject: [PATCH] test(summary): comment utils --- .../comments/_internal/setScrollInfo.spec.ts | 76 +++++++++++++++++++ .../comments/_internal/spoilMeAnyway.spec.ts | 32 ++++++++ .../_internal/spoilerExtension.spec.ts | 35 +++++++++ .../comments/_internal/spoilerExtension.ts | 35 ++++++--- 4 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 projects/client/src/lib/sections/summary/components/comments/_internal/setScrollInfo.spec.ts create mode 100644 projects/client/src/lib/sections/summary/components/comments/_internal/spoilMeAnyway.spec.ts create mode 100644 projects/client/src/lib/sections/summary/components/comments/_internal/spoilerExtension.spec.ts diff --git a/projects/client/src/lib/sections/summary/components/comments/_internal/setScrollInfo.spec.ts b/projects/client/src/lib/sections/summary/components/comments/_internal/setScrollInfo.spec.ts new file mode 100644 index 000000000..c90c11674 --- /dev/null +++ b/projects/client/src/lib/sections/summary/components/comments/_internal/setScrollInfo.spec.ts @@ -0,0 +1,76 @@ +import { setScrollInfo } from '$lib/sections/summary/components/comments/_internal/setScrollInfo.ts'; +import { renderStore } from '$test/beds/store/renderStore.ts'; +import { describe, expect, it, vi } from 'vitest'; + +describe('action: setScrollInfo', () => { + function scrollTo( + node: HTMLDivElement, + location: 'top' | 'middle' | 'bottom', + ) { + vi.spyOn(node, 'scrollHeight', 'get') + .mockReturnValueOnce(100); + + vi.spyOn(node, 'clientHeight', 'get') + .mockReturnValueOnce(50); + + switch (location) { + case 'top': + node.scrollTop = 0; + break; + case 'middle': + node.scrollTop = 25; + break; + case 'bottom': + node.scrollTop = 50; + break; + } + + node.dispatchEvent(new Event('scroll')); + } + + it('should not add scroll info if the node has no overflow', async () => { + const node = document.createElement('div'); + const component = await renderStore(() => setScrollInfo(node)); + + expect(node.classList).not.toContain('scrolled-down'); + expect(node.classList).not.toContain('scrolled-up'); + + component.destroy(); + }); + + it('should indicate it scrolled down to the bottom', async () => { + const node = document.createElement('div'); + const component = await renderStore(() => setScrollInfo(node)); + + scrollTo(node, 'bottom'); + + expect(node.classList).toContain('scrolled-down'); + expect(node.classList).not.toContain('scrolled-up'); + + component.destroy(); + }); + + it('should indicate it scrolled up to the top', async () => { + const node = document.createElement('div'); + const component = await renderStore(() => setScrollInfo(node)); + + scrollTo(node, 'top'); + + expect(node.classList).not.toContain('scrolled-down'); + expect(node.classList).toContain('scrolled-up'); + + component.destroy(); + }); + + it('should indicate it scrolled a little', async () => { + const node = document.createElement('div'); + const component = await renderStore(() => setScrollInfo(node)); + + scrollTo(node, 'middle'); + + expect(node.classList).toContain('scrolled-down'); + expect(node.classList).toContain('scrolled-up'); + + component.destroy(); + }); +}); diff --git a/projects/client/src/lib/sections/summary/components/comments/_internal/spoilMeAnyway.spec.ts b/projects/client/src/lib/sections/summary/components/comments/_internal/spoilMeAnyway.spec.ts new file mode 100644 index 000000000..7573ea441 --- /dev/null +++ b/projects/client/src/lib/sections/summary/components/comments/_internal/spoilMeAnyway.spec.ts @@ -0,0 +1,32 @@ +import { SPOILER_CLASS_NAME } from '$lib/features/spoilers/constants.ts'; +import { spoilMeAnyway } from '$lib/sections/summary/components/comments/_internal/spoilMeAnyway.ts'; +import { renderStore } from '$test/beds/store/renderStore.ts'; +import { describe, expect, it } from 'vitest'; + +describe('action: spoilMeAnyway', () => { + it('should remove spoilers on the entire comment', async () => { + const commentNode = document.createElement('div'); + commentNode.classList.add(SPOILER_CLASS_NAME); + + const component = await renderStore(() => spoilMeAnyway(commentNode)); + commentNode.dispatchEvent(new Event('click')); + + expect(commentNode.classList).not.toContain(SPOILER_CLASS_NAME); + + component.destroy(); + }); + + it('should remove spoilers in a comment', async () => { + const commentNode = document.createElement('div'); + const spoilerNode = document.createElement('div'); + commentNode.appendChild(spoilerNode); + spoilerNode.classList.add(SPOILER_CLASS_NAME); + + const component = await renderStore(() => spoilMeAnyway(commentNode)); + spoilerNode.dispatchEvent(new Event('click', { bubbles: true })); + + expect(spoilerNode.classList).not.toContain(SPOILER_CLASS_NAME); + + component.destroy(); + }); +}); diff --git a/projects/client/src/lib/sections/summary/components/comments/_internal/spoilerExtension.spec.ts b/projects/client/src/lib/sections/summary/components/comments/_internal/spoilerExtension.spec.ts new file mode 100644 index 000000000..19aa200de --- /dev/null +++ b/projects/client/src/lib/sections/summary/components/comments/_internal/spoilerExtension.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { + matchSpoilerTag, + matchSpoilerTagStart, + spoilerRenderer, +} from './spoilerExtension.ts'; + +describe('spoilerExtension', () => { + it('should match the start of a spoiler tag', () => { + const index = matchSpoilerTagStart('[spoiler]'); + expect(index).to.equal(0); + }); + + it('should match the a spoiler tag', () => { + const match = matchSpoilerTag('[spoiler]test[/spoiler]'); + expect(match).to.deep.equal(['[spoiler]test[/spoiler]', 'test']); + }); + + it('should not match the another tag', () => { + const match = matchSpoilerTag('[bold]test[/bold]'); + expect(match).to.deep.equal(null); + }); + + it('should render a spoiler tag', () => { + const renderedResult = spoilerRenderer('test', false); + + expect(renderedResult).to.equal("

test

"); + }); + + it('should not render a spoiler tag if the entire comment is a spoiler', () => { + const renderedResult = spoilerRenderer('test', true); + + expect(renderedResult).to.equal('

test

'); + }); +}); diff --git a/projects/client/src/lib/sections/summary/components/comments/_internal/spoilerExtension.ts b/projects/client/src/lib/sections/summary/components/comments/_internal/spoilerExtension.ts index f3e3cb8a9..da9e9cb6f 100644 --- a/projects/client/src/lib/sections/summary/components/comments/_internal/spoilerExtension.ts +++ b/projects/client/src/lib/sections/summary/components/comments/_internal/spoilerExtension.ts @@ -1,5 +1,27 @@ import type { TokenizerAndRendererExtension } from 'marked'; +export function matchSpoilerTagStart(src: string) { + return src.match(/\[spoiler\]/)?.index; +} + +export function matchSpoilerTag(src: string) { + const rule = /^\[spoiler\](.*?)\[\/spoiler\]/; + return rule.exec(src); +} + +export function spoilerRenderer( + text: string, + isCommentSpoiler: boolean, +) { + if (isCommentSpoiler) { + // If the comment itself is already marked as a spoiler, + // then parse the individual spoilers as a normal paragraph + return `

${text}

`; + } + + return `

${text}

`; +} + export function spoilerExtension( isCommentSpoiler: boolean, ): TokenizerAndRendererExtension { @@ -7,11 +29,10 @@ export function spoilerExtension( name: 'spoiler', level: 'inline', start(src: string) { - return src.match(/\[spoiler\]/)?.index; + return matchSpoilerTagStart(src); }, tokenizer(src) { - const rule = /^\[spoiler\](.*?)\[\/spoiler\]/; - const match = rule.exec(src); + const match = matchSpoilerTag(src); if (match) { const token = { type: 'spoiler', @@ -26,13 +47,7 @@ export function spoilerExtension( return undefined; }, renderer(token) { - if (isCommentSpoiler) { - // If the comment itself is already marked as a spoiler, - // then parse the individual spoilers as a normal paragraph - return `${token.text}

`; - } - - return `

${token.text}

`; + return spoilerRenderer(token.text, isCommentSpoiler); }, }; }