diff --git a/assets/js/__tests__/imagesclientside.spec.ts b/assets/js/__tests__/imagesclientside.spec.ts new file mode 100644 index 00000000..3f3feb88 --- /dev/null +++ b/assets/js/__tests__/imagesclientside.spec.ts @@ -0,0 +1,161 @@ +import { filterNode, initImagesClientside } from '../imagesclientside'; +import { parseSearch } from '../match_query'; +import { matchNone } from '../query/boolean'; +import { assertNotNull } from '../utils/assert'; +import { $ } from '../utils/dom'; + +describe('filterNode', () => { + beforeEach(() => { + window.booru.hiddenTagList = []; + window.booru.spoileredTagList = []; + window.booru.ignoredTagList = []; + window.booru.imagesWithDownvotingDisabled = []; + + window.booru.hiddenFilter = matchNone(); + window.booru.spoileredFilter = matchNone(); + }); + + function makeMediaContainer() { + const element = document.createElement('div'); + element.innerHTML = ` +
+
+ +
+ `; + return [ element, assertNotNull($('.js-spoiler-info-overlay', element)) ]; + } + + it('should show image media boxes not matching any filter', () => { + const [ container, spoilerOverlay ] = makeMediaContainer(); + + filterNode(container); + expect(spoilerOverlay).not.toContainHTML('(Complex Filter)'); + expect(spoilerOverlay).not.toContainHTML('(unknown tag)'); + expect(window.booru.imagesWithDownvotingDisabled).not.toContain('1'); + }); + + it('should spoiler media boxes spoilered by a tag filter', () => { + const [ container, spoilerOverlay ] = makeMediaContainer(); + window.booru.spoileredTagList = [1]; + + filterNode(container); + expect(spoilerOverlay).toContainHTML('(unknown tag)'); + expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); + }); + + it('should spoiler media boxes spoilered by a complex filter', () => { + const [ container, spoilerOverlay ] = makeMediaContainer(); + window.booru.spoileredFilter = parseSearch('id:1'); + + filterNode(container); + expect(spoilerOverlay).toContainHTML('(Complex Filter)'); + expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); + }); + + it('should hide media boxes hidden by a tag filter', () => { + const [ container, spoilerOverlay ] = makeMediaContainer(); + window.booru.hiddenTagList = [1]; + + filterNode(container); + expect(spoilerOverlay).toContainHTML('[HIDDEN]'); + expect(spoilerOverlay).toContainHTML('(unknown tag)'); + expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); + }); + + it('should hide media boxes hidden by a complex filter', () => { + const [ container, spoilerOverlay ] = makeMediaContainer(); + window.booru.hiddenFilter = parseSearch('id:1'); + + filterNode(container); + expect(spoilerOverlay).toContainHTML('[HIDDEN]'); + expect(spoilerOverlay).toContainHTML('(Complex Filter)'); + expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); + }); + + function makeImageBlock(): HTMLElement[] { + const element = document.createElement('div'); + element.innerHTML = ` +
+ + +
+ `; + return [ + element, + assertNotNull($('.image-filtered', element)), + assertNotNull($('.image-show', element)), + assertNotNull($('.filter-explanation', element)) + ]; + } + + it('should show image blocks not matching any filter', () => { + const [ container, imageFiltered, imageShow ] = makeImageBlock(); + + filterNode(container); + expect(imageFiltered).toHaveClass('hidden'); + expect(imageShow).not.toHaveClass('hidden'); + expect(window.booru.imagesWithDownvotingDisabled).not.toContain('1'); + }); + + it('should spoiler image blocks spoilered by a tag filter', () => { + const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); + window.booru.spoileredTagList = [1]; + + filterNode(container); + expect(imageFiltered).not.toHaveClass('hidden'); + expect(imageShow).toHaveClass('hidden'); + expect(filterExplanation).toContainHTML('spoilered by'); + expect(filterExplanation).toContainHTML('(unknown tag)'); + expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); + }); + + it('should spoiler image blocks spoilered by a complex filter', () => { + const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); + window.booru.spoileredFilter = parseSearch('id:1'); + + filterNode(container); + expect(imageFiltered).not.toHaveClass('hidden'); + expect(imageShow).toHaveClass('hidden'); + expect(filterExplanation).toContainHTML('spoilered by'); + expect(filterExplanation).toContainHTML('complex tag expression'); + expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); + }); + + it('should hide image blocks hidden by a tag filter', () => { + const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); + window.booru.hiddenTagList = [1]; + + filterNode(container); + expect(imageFiltered).not.toHaveClass('hidden'); + expect(imageShow).toHaveClass('hidden'); + expect(filterExplanation).toContainHTML('hidden by'); + expect(filterExplanation).toContainHTML('(unknown tag)'); + expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); + }); + + it('should hide image blocks hidden by a complex filter', () => { + const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); + window.booru.hiddenFilter = parseSearch('id:1'); + + filterNode(container); + expect(imageFiltered).not.toHaveClass('hidden'); + expect(imageShow).toHaveClass('hidden'); + expect(filterExplanation).toContainHTML('hidden by'); + expect(filterExplanation).toContainHTML('complex tag expression'); + expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); + }); + +}); + +describe('initImagesClientside', () => { + it('should initialize the imagesWithDownvotingDisabled array', () => { + initImagesClientside(); + expect(window.booru.imagesWithDownvotingDisabled).toEqual([]); + }); +}); diff --git a/assets/js/imagesclientside.ts b/assets/js/imagesclientside.ts index 61e1d1ac..add108c9 100644 --- a/assets/js/imagesclientside.ts +++ b/assets/js/imagesclientside.ts @@ -2,6 +2,7 @@ * Client-side image filtering/spoilering. */ +import { assertNotUndefined } from './utils/assert'; import { $$, escapeHtml } from './utils/dom'; import { setupInteractions } from './interactions'; import { showThumb, showBlock, spoilerThumb, spoilerBlock, hideThumb } from './utils/image'; @@ -15,20 +16,20 @@ function run( img: HTMLDivElement, tags: TagData[], complex: AstMatcher, - callback: RunCallback, + runCallback: RunCallback ): boolean { const hit = (() => { // Check tags array first to provide more precise filter explanations const hitTags = imageHitsTags(img, tags); if (hitTags.length !== 0) { - callback(img, hitTags, 'tags'); + runCallback(img, hitTags, 'tags'); return true; } // No tags matched, try complex filter AST const hitComplex = imageHitsComplex(img, complex); if (hitComplex) { - callback(img, hitTags, 'complex'); + runCallback(img, hitTags, 'complex'); return true; } @@ -36,9 +37,9 @@ function run( return false; })(); - if (hit && img.dataset.imageId) { + if (hit) { // Disallow negative interaction on image which is not visible - window.booru.imagesWithDownvotingDisabled.push(img.dataset.imageId); + window.booru.imagesWithDownvotingDisabled.push(assertNotUndefined(img.dataset.imageId)); } return hit;