Skip to content

Commit

Permalink
🧪 test: improve tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ShayanTheNerd committed Aug 17, 2024
1 parent 9770599 commit aebefce
Show file tree
Hide file tree
Showing 16 changed files with 107 additions and 149 deletions.
22 changes: 3 additions & 19 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ const vitestRules = {
'vitest/no-focused-tests': 'error',
'vitest/no-disabled-tests': 'error',
'vitest/prefer-to-contain': 'error',
'vitest/consistent-test-it': 'error',
'vitest/prefer-called-with': 'error',
'vitest/prefer-to-be-falsy': 'error',
'vitest/no-duplicate-hooks': 'error',
Expand All @@ -176,12 +175,12 @@ const vitestRules = {
'vitest/consistent-test-filename': 'error',
'vitest/no-test-return-statement': 'error',
'vitest/require-to-throw-message': 'error',
'vitest/require-top-level-describe': 'error',
'vitest/prefer-comparison-matcher': 'error',
'vitest/no-interpolation-in-snapshots': 'error',
'vitest/prefer-mock-promise-shorthand': 'error',
'vitest/prefer-snapshot-hint': ['error', 'always'],
'vitest/max-nested-describe': ['error', { max: 1 }],
'vitest/consistent-test-it': ['error', { fn: 'test', withinDescribe: 'test' }],
};

const cypressRules = {
Expand Down Expand Up @@ -307,25 +306,10 @@ export default eslintAntfuConfig(
{
files: ['tests/unit/**/*.test.ts'],
plugins: { eslintPluginVitest },
settings: {
vitest: {
typecheck: true,
},
},
languageOptions: {
globals: {
...eslintPluginVitest.environments.env.globals,
},
},
rules: {
...vitestRules,
'ts/no-unsafe-call': 'off',
'no-magic-numbers': 'off',
'ts/no-unsafe-member-access': 'off',
},
rules: { ...vitestRules, 'no-magic-numbers': 'off' },
},
{
files: ['tests/cypress/**/*.ts'],
files: ['tests/cypress/e2e/**/*.cy.ts'],
...eslintPluginCypress.configs.recommended,
rules: { ...cypressRules, 'no-magic-numbers': 'off' },
},
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"type": "module",
"packageManager": "pnpm@8.15.4",
"packageManager": "pnpm@9.7.1",
"author": {
"name": "Shayan Zamani",
"url": "https://shayan-zamani.me/",
Expand Down Expand Up @@ -41,7 +41,7 @@
"astro": "^4.4.15",
"browserslist": "^4.23.0",
"concurrently": "^8.2.2",
"cypress": "^13.13.2",
"cypress": "^13.13.3",
"cypress-vite": "^1.5.0",
"eslint": "^9.8.0",
"eslint-plugin-cypress": "^3.4.0",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 30 additions & 23 deletions tests/cypress/e2e/image-editor.cy.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,71 @@
import { activeFilterClass, imgDownloadTimeoutMS } from '@ts/constants.ts';

before(() => {
const FIXTURES_PATH = 'tests/cypress/fixtures';
const DOWNLOADS_PATH = 'tests/cypress/downloads';
const INITIAL_CSS_MATRIX = 'matrix(1, 0, 0, 1, 0, 0)';
const INITIAL_CSS_FILTERS = 'brightness(1) grayscale(0) blur(0px) hue-rotate(0deg) opacity(1) contrast(1) saturate(1) sepia(0)';

beforeEach(() => {
cy.visit('/');

cy.get('[data-test="rotate_left"]').as('rotateLeftBtn');
cy.get('[data-test="vertical_flip"]').as('verticalFlipBtn');
cy.get('[data-test="rotate_right"]').as('rotateRightBtn');
cy.get('[data-test="grayscale"]').as('grayscaleFilterBtn');
cy.get('[data-test="img_select_label"]').as('imgSelectLabel');
cy.get('#filters_container').children().first().as('firstFilterBtn');
});

it('goes through the process of uploading, editing, and downloading multiple images', () => {
/* Initial state */
it('initial UI state', () => {
cy.get('#edit_options_container').should('be.disabled');
cy.get('#img_save_anchor').should('have.attr', 'aria-disabled', 'true');
});

/* Upload an image */
cy.get('@imgSelectLabel').selectFile('tests/cypress/fixtures/sh.png').trigger('cancel');
cy.get('@imgSelectLabel').selectFile('tests/cypress/fixtures/pickle-rick.webp');
it('upload, edit, and download multiple images', () => {
/* Upload an image. */
cy.get('@imgSelectLabel').selectFile(`${FIXTURES_PATH}/pickle-rick.webp`).trigger('cancel');
cy.get('@imgSelectLabel').selectFile(`${FIXTURES_PATH}/pickle-rick.webp`);
cy.get('#edit_options_container').should('be.enabled');
cy.get('#reset_filters_btn').should('be.disabled');
cy.get('#img_save_anchor').should('have.attr', 'aria-disabled', 'true');
cy.get('#img').should('have.attr', 'alt', 'pickle-rick.webp').and('have.attr', 'title', 'pickle-rick.webp');

/* Edit the uploaded image */
/* Edit the uploaded image. */
cy.get('@verticalFlipBtn').click();
cy.get('@rotateRightBtn').click();
cy.get('@grayscaleFilterBtn').click();
cy.get('@firstFilterBtn').should('not.have.class', activeFilterClass);
cy.get('@grayscaleFilterBtn').should('have.class', activeFilterClass);
cy.get('#active_filter_range_input').invoke('val', 100);
cy.get('#active_filter_range_input').trigger('input');
cy.get('#img').should('have.css', 'filter', 'brightness(1) grayscale(1) blur(0px) hue-rotate(0deg) opacity(1) contrast(1) saturate(1) sepia(0)');
cy.get('#reset_filters_btn').should('be.enabled');
cy.get('#img_save_anchor').should('have.attr', 'aria-disabled', 'false');
cy.get('@rotateRightBtn').click();
cy.get('@verticalFlipBtn').click();
cy.get('@firstFilterBtn').should('not.have.class', activeFilterClass);
cy.get('@grayscaleFilterBtn').should('have.class', activeFilterClass);
cy.get('#img').should('have.css', 'transform', 'matrix(0, 1, 1, 0, 0, 0)');
cy.get('#img').should('have.css', 'filter', 'brightness(1) grayscale(1) blur(0px) hue-rotate(0deg) opacity(1) contrast(1) saturate(1) sepia(0)');

/* Download the edited image */
/* Download the edited image. */
cy.get('#img_save_anchor').click();
cy.get('#img_save_anchor').should('have.text', 'Saving...');
cy.get('#img_save_anchor').should('have.attr', 'aria-disabled', 'true');
cy.wait(imgDownloadTimeoutMS);
cy.get('#img_save_anchor').should('have.text', 'Save Image');
cy.readFile('tests/cypress/downloads/pickle-rick (edited).webp');
cy.readFile(`${DOWNLOADS_PATH}/pickle-rick (edited).webp`).should('exist');

/* Upload another image */
cy.get('#img_drop_zone').selectFile('tests/cypress/fixtures/landscape.jpg', { action: 'drag-drop' });
cy.get('@grayscaleFilterBtn').should('not.have.class', activeFilterClass);
/* Upload another image. */
cy.get('#img_drop_zone').selectFile(`${FIXTURES_PATH}/landscape.jpg`, { action: 'drag-drop' });
cy.get('@firstFilterBtn').should('have.class', activeFilterClass);

cy.get('@grayscaleFilterBtn').should('not.have.class', activeFilterClass);
cy.get('#filters_container').should(({ 0: filtersContainer }) => {
expect(filtersContainer?.scrollLeft).to.equal(0);
});

/* Manually reset edits */
cy.get('@rotateLeftBtn').click();
cy.get('#img').should('have.css', 'transform', 'matrix(0, -1, 1, 0, 0, 0)');
/* Manually reset applied edits. */
cy.get('@rotateRightBtn').click();
cy.get('@grayscaleFilterBtn').click();
cy.get('#active_filter_range_input').invoke('val', 100);
cy.get('#active_filter_range_input').trigger('input');
cy.get('#reset_filters_btn').click();
cy.get('#img').should('have.css', 'transform', 'matrix(1, 0, 0, 1, 0, 0)');
cy.get('#reset_filters_btn').should('be.disabled');
cy.get('#img_save_anchor').should('have.attr', 'aria-disabled', 'true');
cy.get('#img').should('have.css', 'transform', INITIAL_CSS_MATRIX);
cy.get('#img').should('have.css', 'filter', INITIAL_CSS_FILTERS);
});
Binary file removed tests/cypress/fixtures/sh.png
Binary file not shown.
12 changes: 5 additions & 7 deletions tests/unit/imgStore/activeFilter.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { test, expect } from 'vitest';
import { imgStore } from '@ts/imgStore.ts';
import { it, expect, describe } from 'vitest';

describe('imgStore.activeFilter', () => {
it('should update the active filter based on the given name, and allow access to its properties', () => {
const activeFilterName = 'grayscale';
imgStore.activeFilter = activeFilterName;
test('updates the active filter based on the given name, and allows access to its properties', () => {
const activeFilterName = 'grayscale';
imgStore.activeFilter = activeFilterName;

expect(imgStore.activeFilter.name).toBe(activeFilterName);
});
expect(imgStore.activeFilter.name).toBe(activeFilterName);
});
14 changes: 5 additions & 9 deletions tests/unit/imgStore/isEdited.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { test, expect } from 'vitest';
import { imgStore } from '@ts/imgStore.ts';
import { it, expect, describe } from 'vitest';

describe('imgStore.isEdited', () => {
it('should return “true” if the image has been rotated', () => {
const rotationDegs = [-450, -270, -180, -90, 90, 180, 270, 450] as const;
const rotationDegs = [-450, -270, -180, -90, 90, 180, 270, 450] as const;

rotationDegs.forEach((rotationDeg) => {
imgStore.state.rotationDeg = rotationDeg;
test.each(rotationDegs)('%i° rotates the image', (rotationDeg) => {
imgStore.state.rotationDeg = rotationDeg;

expect(imgStore.isEdited).toBeTruthy();
});
});
expect(imgStore.isEdited).toBeTruthy();
});
14 changes: 5 additions & 9 deletions tests/unit/imgStore/isLandscape.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { test, expect } from 'vitest';
import { imgStore } from '@ts/imgStore.ts';
import { it, expect, describe } from 'vitest';

describe('imgStore.isLandscape', () => {
it('should return “true” if the image has been rotated only by multiples of 90 degrees and not 180 degrees', () => {
const rotationDegs = [-450, -270, -90, 90, 270, 450] as const;
const rotationDegs = [-450, -270, -90, 90, 270, 450] as const;

rotationDegs.forEach((rotationDeg) => {
imgStore.state.rotationDeg = rotationDeg;
test.each(rotationDegs)('%i° is a multiple of 90°, but not that of 180°; so the image is landscaped', (rotationDeg) => {
imgStore.state.rotationDeg = rotationDeg;

expect(imgStore.isLandscape).toBeTruthy();
});
});
expect(imgStore.isLandscape).toBeTruthy();
});
18 changes: 8 additions & 10 deletions tests/unit/imgStore/reset.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { test, expect } from 'vitest';
import { imgStore } from '@ts/imgStore.ts';
import { it, expect, describe } from 'vitest';
import { deepClone } from '@ts/utils/deepClone.ts';
import { resetRotationDeg } from '@ts/utils/resetRotationDeg.ts';

describe('imgStore.reset', () => {
it('should correctly reset the state', () => {
const initialState = deepClone(imgStore.state);
const [name, extension, rotationDeg] = ['New Image', 'png', 180];
const adjustedRotationDeg = resetRotationDeg(rotationDeg);
test('resets the state', () => {
const initialState = deepClone(imgStore.state);
const [name, extension, rotationDeg] = ['New Image', 'png', 180];
const adjustedRotationDeg = resetRotationDeg(rotationDeg);

Object.assign(imgStore.state, { name, extension, rotationDeg, verticalFlip: -1 });
imgStore.reset();
Object.assign(imgStore.state, { name, extension, rotationDeg, verticalFlip: -1 });
imgStore.reset();

expect(imgStore.state).toStrictEqual({ ...initialState, name, extension, rotationDeg: adjustedRotationDeg });
});
expect(imgStore.state).toStrictEqual({ ...initialState, name, extension, rotationDeg: adjustedRotationDeg });
});
10 changes: 4 additions & 6 deletions tests/unit/imgStore/title.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { test, expect } from 'vitest';
import { imgStore } from '@ts/imgStore.ts';
import { it, expect, describe } from 'vitest';

describe('imgStore.title', () => {
it('should set the name and extension of the image file, and return its full name (name.extension)', () => {
Object.assign(imgStore.state, { name: 'New Image', extension: 'png' });
test('sets the name and the extension of the image file, then returns “name.extension”', () => {
Object.assign(imgStore.state, { name: 'New Image', extension: 'png' });

expect(imgStore.title).toMatchInlineSnapshot(`"New Image.png"`);
});
expect(imgStore.title).toMatchInlineSnapshot(`"New Image.png"`);
});
14 changes: 6 additions & 8 deletions tests/unit/imgStore/updateCSSFilters.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { test, expect } from 'vitest';
import { imgStore } from '@ts/imgStore.ts';
import { it, expect, describe } from 'vitest';

describe('imgStore.updateCSSFilters', () => {
it('should create and/or update the CSS filters string corresponding with filter values', () => {
imgStore.state.filters.find(({ name }) => name === 'grayscale')!.value = 50;
imgStore.updateCSSFilters();
const { CSSFilters } = imgStore.state;
test('creates the CSS filters string corresponding with filter values', () => {
imgStore.state.filters.find(({ name }) => name === 'grayscale')!.value = 50;
imgStore.updateCSSFilters();
const { CSSFilters } = imgStore.state;

expect(CSSFilters).toMatchInlineSnapshot(`"brightness(100%)grayscale(50%)blur(0px)hue-rotate(0deg)opacity(100%)contrast(100%)saturate(100%)sepia(0%)"`);
});
expect(CSSFilters).toMatchInlineSnapshot(`"brightness(100%)grayscale(50%)blur(0px)hue-rotate(0deg)opacity(100%)contrast(100%)saturate(100%)sepia(0%)"`);
});
25 changes: 11 additions & 14 deletions tests/unit/imgStore/updateSpinValue.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { test, expect } from 'vitest';
import { imgStore } from '@ts/imgStore.ts';
import { spinModes } from '@ts/constants.ts';
import { it, expect, describe } from 'vitest';
import { spinIsRotation } from '@ts/utils/spinIsRotation.ts';

describe('imgStore.updateSpinValue', () => {
it('should update the rotation degree and vertical/horizontal flip values', () => {
spinModes.forEach((spinMode) => {
imgStore.updateSpinValue(spinMode);
test.each(spinModes)('“%s” updates the rotation degree or vertical/horizontal flip', (spinMode) => {
imgStore.updateSpinValue(spinMode);

const { rotationDeg, verticalFlip, horizontalFlip } = imgStore.state;
const { rotationDeg, verticalFlip, horizontalFlip } = imgStore.state;

if (spinIsRotation(spinMode)) {
expect(rotationDeg === 0 || rotationDeg % 90 === 0).toBeTruthy();
} else {
expect([1, -1]).toContain(verticalFlip);
expect([1, -1]).toContain(horizontalFlip);
}
});
});
// eslint-disable-next-line test/no-conditional-in-test
if (spinIsRotation(spinMode)) {
expect(rotationDeg === 0 || rotationDeg % 90 === 0).toBeTruthy();
} else {
expect([1, -1]).toContain(verticalFlip);
expect([1, -1]).toContain(horizontalFlip);
}
});
26 changes: 12 additions & 14 deletions tests/unit/utils/deepClone.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { it, expect, describe } from 'vitest';
import { test, expect } from 'vitest';
import { deepClone } from '@ts/utils/deepClone.ts';

describe('deepClone', () => {
it('should create a deep clone of the given object', () => {
const obj = {
a: 1,
b: true,
c: 'value',
d: { e: 'f' },
g: [1, '2', { h: '3' }],
};
const clone = deepClone(obj);
test('creates a deep clone of the given object', () => {
const obj = {
a: 1,
b: true,
c: 'value',
d: { e: 'f' },
g: [1, '2', { h: '3' }],
};
const clone = deepClone(obj);

expect(clone).not.toBe(obj);
expect(clone).toStrictEqual(obj);
});
expect(clone).not.toBe(obj);
expect(clone).toStrictEqual(obj);
});
16 changes: 6 additions & 10 deletions tests/unit/utils/resetRotationDeg.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { it, expect, describe } from 'vitest';
import { test, expect } from 'vitest';
import { resetRotationDeg } from '@ts/utils/resetRotationDeg.ts';
import { rotationDegs as baseRotationDegs } from '@ts/constants.ts';

describe('resetRotationDeg', () => {
it('should reset the rotation degree to a multiple of 360 degrees', () => {
const fullRotationDeg = 360;
const rotationDegs = [-450, -360, -270, -180, -90, 0, 90, 180, 270, 360, 450];
const rotationDegs = [-450, -360, -270, -180, -90, 0, 90, 180, 270, 360, 450];

rotationDegs.forEach((rotationDeg) => {
const adjustedRotationDeg = resetRotationDeg(rotationDeg);
test.each(rotationDegs)('%i° is reset to a multiple of 360°', (rotationDeg) => {
const adjustedRotationDeg = resetRotationDeg(rotationDeg);

expect(adjustedRotationDeg % fullRotationDeg).toBe(0);
});
});
expect(adjustedRotationDeg % baseRotationDegs.full).toBe(0);
});
Loading

0 comments on commit aebefce

Please sign in to comment.