From 8ca4a99a1c446d775d8c49ce174493d60ecda982 Mon Sep 17 00:00:00 2001 From: SeinopSys Date: Sun, 3 Apr 2022 03:01:15 +0200 Subject: [PATCH] fix coverage for image utils --- assets/js/utils/__tests__/image.spec.ts | 112 ++++++++++++++++++------ assets/js/utils/image.ts | 17 ++-- 2 files changed, 94 insertions(+), 35 deletions(-) diff --git a/assets/js/utils/__tests__/image.spec.ts b/assets/js/utils/__tests__/image.spec.ts index 19d4bf65..40d03a5a 100644 --- a/assets/js/utils/__tests__/image.spec.ts +++ b/assets/js/utils/__tests__/image.spec.ts @@ -54,6 +54,22 @@ describe('Image utils', () => { mockSpoilerOverlay, }; }; + const imageFilteredClass = 'image-filtered'; + const imageShowClass = 'image-show'; + const spoilerPendingClass = 'spoiler-pending'; + const createImageFilteredElement = (mockElement: HTMLDivElement) => { + const mockFilteredImageElement = document.createElement('div'); + mockFilteredImageElement.classList.add(imageFilteredClass); + mockElement.appendChild(mockFilteredImageElement); + return { mockFilteredImageElement }; + }; + const createImageShowElement = (mockElement: HTMLDivElement) => { + const mockShowElement = document.createElement('div'); + mockShowElement.classList.add(imageShowClass); + mockShowElement.classList.add(hiddenClass); + mockElement.appendChild(mockShowElement); + return { mockShowElement }; + }; describe('showThumb', () => { let mockServeHidpiValue: string | null = null; @@ -146,6 +162,26 @@ describe('Image utils', () => { expect(result).toBe(true); }); + ['data-size', 'data-uris'].forEach(missingAttributeName => { + it(`should return early if the ${missingAttributeName} attribute is missing`, () => { + const { mockElement } = createMockElements({ + extension: 'webm', + }); + const jsonParseSpy = jest.spyOn(JSON, 'parse'); + + mockElement.removeAttribute(missingAttributeName); + + try { + const result = showThumb(mockElement); + expect(result).toBe(false); + expect(jsonParseSpy).not.toHaveBeenCalled(); + } + finally { + jsonParseSpy.mockRestore(); + } + }); + }); + it('should return early if there is no video element', () => { const { mockElement, mockVideo, playSpy } = createMockElements({ extension: 'webm', @@ -304,24 +340,21 @@ describe('Image utils', () => { const result = showThumb(mockElement); expect(result).toBe(false); }); + + it('should return false if overlay is missing', () => { + const { mockElement, mockSpoilerOverlay } = createMockElementWithPicture('jpg'); + mockSpoilerOverlay.parentNode?.removeChild(mockSpoilerOverlay); + const result = showThumb(mockElement); + expect(result).toBe(false); + }); }); describe('showBlock', () => { - const imageFilteredClass = 'image-filtered'; - const imageShowClass = 'image-show'; - const spoilerPendingClass = 'spoiler-pending'; - it('should hide the filtered image element and show the image', () => { const mockElement = document.createElement('div'); - const mockFilteredImageElement = document.createElement('div'); - mockFilteredImageElement.classList.add(imageFilteredClass); - mockElement.appendChild(mockFilteredImageElement); - - const mockShowElement = document.createElement('div'); - mockShowElement.classList.add(imageShowClass); - mockShowElement.classList.add(hiddenClass); - mockElement.appendChild(mockShowElement); + const { mockFilteredImageElement } = createImageFilteredElement(mockElement); + const { mockShowElement } = createImageShowElement(mockElement); showBlock(mockElement); @@ -329,6 +362,18 @@ describe('Image utils', () => { expect(mockShowElement).not.toHaveClass(hiddenClass); expect(mockShowElement).toHaveClass(spoilerPendingClass); }); + + it('should not throw if image-filtered element is missing', () => { + const mockElement = document.createElement('div'); + createImageShowElement(mockElement); + expect(() => showBlock(mockElement)).not.toThrow(); + }); + + it('should not throw if image-show element is missing', () => { + const mockElement = document.createElement('div'); + createImageFilteredElement(mockElement); + expect(() => showBlock(mockElement)).not.toThrow(); + }); }); describe('hideThumb', () => { @@ -528,9 +573,7 @@ describe('Image utils', () => { }); describe('spoilerBlock', () => { - const imageFilteredClass = 'image-filtered'; const filterExplanationClass = 'filter-explanation'; - const imageShowClass = 'image-show'; const createFilteredImageElement = () => { const mockImageFiltered = document.createElement('div'); mockImageFiltered.classList.add(imageFilteredClass, hiddenClass); @@ -541,22 +584,31 @@ describe('Image utils', () => { return { mockImageFiltered, mockImage }; }; - - it('should do nothing if image element is missing', () => { + const createMockElement = (appendImageShow = true, appendImageFiltered = true) => { const mockElement = document.createElement('div'); + const { mockImageFiltered, mockImage } = createFilteredImageElement(); + if (appendImageFiltered) mockElement.appendChild(mockImageFiltered); + const mockExplanation = document.createElement('span'); + mockExplanation.classList.add(filterExplanationClass); + mockElement.appendChild(mockExplanation); + + const mockImageShow = document.createElement('div'); + mockImageShow.classList.add(imageShowClass); + if (appendImageShow) mockElement.appendChild(mockImageShow); + + return { mockElement, mockImage, mockExplanation, mockImageShow, mockImageFiltered }; + }; + + it('should not throw if image element is missing', () => { + const mockElement = document.createElement('div'); + const { mockImageFiltered, mockImage } = createFilteredImageElement(); + mockImage.parentNode?.removeChild(mockImage); + mockElement.appendChild(mockImageFiltered); expect(() => spoilerBlock(mockElement, mockSpoilerUri, mockSpoilerReason)).not.toThrow(); }); it('should update the elements with the parameters and set classes if image element is found', () => { - const mockElement = document.createElement('div'); - const { mockImageFiltered, mockImage } = createFilteredImageElement(); - mockElement.appendChild(mockImageFiltered); - const mockExplanation = document.createElement('span'); - mockExplanation.classList.add(filterExplanationClass); - mockElement.appendChild(mockExplanation); - const mockImageShow = document.createElement('div'); - mockImageShow.classList.add(imageShowClass); - mockElement.appendChild(mockImageShow); + const { mockElement, mockImage, mockExplanation, mockImageShow, mockImageFiltered } = createMockElement(); spoilerBlock(mockElement, mockSpoilerUri, mockSpoilerReason); @@ -565,5 +617,15 @@ describe('Image utils', () => { expect(mockImageShow).toHaveClass(hiddenClass); expect(mockImageFiltered).not.toHaveClass(hiddenClass); }); + + it('should not throw if image-filtered element is missing', () => { + const { mockElement } = createMockElement(true, false); + expect(() => spoilerBlock(mockElement, mockSpoilerUri, mockSpoilerReason)).not.toThrow(); + }); + + it('should not throw if image-show element is missing', () => { + const { mockElement } = createMockElement(false, true); + expect(() => spoilerBlock(mockElement, mockSpoilerUri, mockSpoilerReason)).not.toThrow(); + }); }); }); diff --git a/assets/js/utils/image.ts b/assets/js/utils/image.ts index f8cc5cbc..c0e35543 100644 --- a/assets/js/utils/image.ts +++ b/assets/js/utils/image.ts @@ -1,11 +1,7 @@ import { clearEl } from './dom'; import store from './store'; -function showVideoThumb(img: HTMLDivElement) { - const size = img.dataset.size; - const urisString = img.dataset.uris; - if (!size || !urisString) return; - +function showVideoThumb(img: HTMLDivElement, size: string, urisString: string) { const uris = JSON.parse(urisString); const thumbUri = uris[size]; @@ -33,13 +29,13 @@ function showVideoThumb(img: HTMLDivElement) { export function showThumb(img: HTMLDivElement) { const size = img.dataset.size; const urisString = img.dataset.uris; - if (!size || !urisString) return; + if (!size || !urisString) return false; const uris = JSON.parse(urisString); const thumbUri = uris[size].replace(/webm$/, 'gif'); const picEl = img.querySelector('picture'); - if (!picEl) return showVideoThumb(img); + if (!picEl) return showVideoThumb(img, size, urisString); const imgEl = picEl.querySelector('img'); if (!imgEl || imgEl.src.indexOf(thumbUri) !== -1) return false; @@ -53,7 +49,7 @@ export function showThumb(img: HTMLDivElement) { imgEl.src = thumbUri; const overlay = img.querySelector('.js-spoiler-info-overlay'); - if (!overlay) return; + if (!overlay) return false; if (uris[size].indexOf('.webm') !== -1) { overlay.classList.remove('hidden'); @@ -130,7 +126,8 @@ export function spoilerThumb(img: HTMLDivElement, spoilerUri: string, reason: st } export function spoilerBlock(img: HTMLDivElement, spoilerUri: string, reason: string) { - const imgEl = img.querySelector('.image-filtered img'); + const imgFiltered = img.querySelector('.image-filtered'); + const imgEl = imgFiltered?.querySelector('img'); const imgReason = img.querySelector('.filter-explanation'); if (!imgEl) return; @@ -141,5 +138,5 @@ export function spoilerBlock(img: HTMLDivElement, spoilerUri: string, reason: st } img.querySelector('.image-show')?.classList.add('hidden'); - img.querySelector('.image-filtered')?.classList.remove('hidden'); + if (imgFiltered) imgFiltered.classList.remove('hidden'); }