From 194b15348e815c8a33acbf26688cb8aec9f29051 Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Fri, 9 May 2025 11:20:40 -0400 Subject: [PATCH] style: update to ESLint v9 and rework lint config --- .eslintignore | 6 -- .eslintrc.cjs | 45 --------------- .github/dependabot.yml | 9 --- .prettierignore | 3 - .prettierrc.yaml | 9 --- eslint.config.js | 95 ++++++++++++++++++++++++++++++++ jest.config.js | 6 +- package.json | 20 ++++--- prettier.config.js | 14 +++++ src/core/index.js | 14 +---- src/index.js | 1 - src/pure.js | 17 ++++-- src/vite.js | 12 ++-- tests/_env.js | 2 +- tests/act.test.js | 10 ++-- tests/context.test.js | 6 +- tests/events.test.js | 10 ++-- tests/fixtures/Typed.svelte | 2 +- tests/fixtures/TypedRunes.svelte | 2 +- tests/render.test.js | 27 ++++----- tests/rerender.test.js | 4 +- tests/transition.test.js | 13 +++-- 22 files changed, 182 insertions(+), 145 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.cjs delete mode 100644 .prettierrc.yaml create mode 100644 eslint.config.js create mode 100644 prettier.config.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 39f9d9e..0000000 --- a/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -scripts/* -.eslintignore -.prettierignore -.github/workflows/* -*.md -types diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 77f8b7c..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,45 +0,0 @@ -module.exports = { - root: true, - env: { - browser: true, - es6: true, - }, - extends: ['standard', 'plugin:svelte/recommended', 'prettier'], - plugins: ['svelte', 'simple-import-sort'], - rules: { - 'simple-import-sort/imports': 'error', - 'simple-import-sort/exports': 'error', - }, - overrides: [ - { - files: ['*.svelte'], - parser: 'svelte-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser', - }, - rules: { - 'no-undef-init': 'off', - 'prefer-const': 'off', - 'svelte/no-unused-svelte-ignore': 'off', - }, - }, - { - files: ['*.ts'], - parser: '@typescript-eslint/parser', - parserOptions: { - project: './tsconfig.json', - }, - extends: [ - 'plugin:@typescript-eslint/strict-type-checked', - 'plugin:@typescript-eslint/stylistic-type-checked', - 'prettier', - ], - }, - ], - parserOptions: { - ecmaVersion: 2022, - sourceType: 'module', - }, - globals: { afterEach: 'readonly', $state: 'readonly', $props: 'readonly' }, - ignorePatterns: ['!/.*'], -} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c9eb8a7..434a8bb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,15 +23,6 @@ updates: development: dependency-type: 'development' - # TODO(mcous, 2024-04-30): update to ESLint v9 + flat config - ignore: - - dependency-name: 'eslint' - versions: ['>=9'] - - dependency-name: 'eslint-plugin-n' - versions: ['>=17'] - - dependency-name: 'eslint-plugin-promise' - versions: ['>=7'] - # Update GitHub Actions dependencies - package-ecosystem: 'github-actions' directory: '/' diff --git a/.prettierignore b/.prettierignore index 5e50c20..c750ecf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1 @@ -scripts/* -.eslintignore -.prettierignore .all-contributorsrc diff --git a/.prettierrc.yaml b/.prettierrc.yaml deleted file mode 100644 index 0a2ace3..0000000 --- a/.prettierrc.yaml +++ /dev/null @@ -1,9 +0,0 @@ -semi: false -singleQuote: true -trailingComma: es5 -plugins: - - prettier-plugin-svelte -overrides: - - files: '*.svelte' - options: - parser: svelte diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..c03de8c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,95 @@ +import js from '@eslint/js' +import eslintPluginVitest from '@vitest/eslint-plugin' +import eslintConfigPrettier from 'eslint-config-prettier' +import eslintPluginJestDom from 'eslint-plugin-jest-dom' +import eslintPluginPromise from 'eslint-plugin-promise' +import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort' +import eslintPluginSvelte from 'eslint-plugin-svelte' +import eslintPluginTestingLibrary from 'eslint-plugin-testing-library' +import eslintPluginUnicorn from 'eslint-plugin-unicorn' +import globals from 'globals' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + js.configs.recommended, + tseslint.configs.strict, + tseslint.configs.stylistic, + eslintPluginUnicorn.configs['flat/recommended'], + eslintPluginPromise.configs['flat/recommended'], + eslintPluginSvelte.configs['flat/recommended'], + eslintPluginSvelte.configs['flat/prettier'], + eslintConfigPrettier, + { + name: 'settings', + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { + parser: tseslint.parser, + extraFileExtensions: ['.svelte'], + }, + globals: { + ...globals.browser, + ...globals.node, + ...globals.jest, + }, + }, + }, + { + name: 'ignores', + ignores: ['coverage', 'types'], + }, + { + name: 'simple-import-sort', + plugins: { + 'simple-import-sort': eslintPluginSimpleImportSort, + }, + rules: { + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', + }, + }, + { + name: 'tests', + files: ['**/*.test.js'], + extends: [ + eslintPluginVitest.configs.recommended, + eslintPluginJestDom.configs['flat/recommended'], + eslintPluginTestingLibrary.configs['flat/dom'], + ], + rules: { + 'testing-library/no-node-access': [ + 'error', + { allowContainerFirstChild: true }, + ], + }, + }, + { + name: 'extras', + rules: { + 'unicorn/prevent-abbreviations': 'off', + }, + }, + { + name: 'svelte-extras', + files: ['**/*.svelte'], + rules: { + 'svelte/no-unused-svelte-ignore': 'off', + 'unicorn/filename-case': ['error', { case: 'pascalCase' }], + 'unicorn/no-useless-undefined': 'off', + }, + }, + { + name: 'ts-extras', + files: ['**/*.ts'], + extends: [ + tseslint.configs.strictTypeChecked, + tseslint.configs.stylisticTypeChecked, + ], + languageOptions: { + parserOptions: { + projectService: true, + }, + }, + } +) diff --git a/jest.config.js b/jest.config.js index ff518c4..b590229 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,9 @@ import { VERSION as SVELTE_VERSION } from 'svelte/compiler' const SVELTE_TRANSFORM_PATTERN = - SVELTE_VERSION >= '5' ? '^.+\\.svelte(?:\\.js)?$' : '^.+\\.svelte$' + SVELTE_VERSION >= '5' + ? String.raw`^.+\.svelte(?:\.js)?$` + : String.raw`^.+\.svelte$` export default { testMatch: ['/tests/**/*.test.js'], @@ -15,7 +17,7 @@ export default { injectGlobals: false, moduleNameMapper: { '^vitest$': '/tests/_jest-vitest-alias.js', - '^@testing-library\\/svelte$': '/src/index.js', + [String.raw`^@testing-library\/svelte$`]: '/src/index.js', }, resetMocks: true, restoreMocks: true, diff --git a/package.json b/package.json index 7e50de7..fea66a7 100644 --- a/package.json +++ b/package.json @@ -90,24 +90,25 @@ "@testing-library/dom": "9.x.x || 10.x.x" }, "devDependencies": { + "@eslint/js": "^9.26.0", "@jest/globals": "^29.7.0", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", "@vitest/coverage-v8": "^3.1.3", + "@vitest/eslint-plugin": "^1.1.44", "all-contributors-cli": "^6.26.1", "doctoc": "^2.2.1", - "eslint": "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-n": "^16.6.2", - "eslint-plugin-promise": "^6.4.0", + "eslint": "^9.26.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-jest-dom": "^5.5.0", + "eslint-plugin-promise": "^7.2.1", "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-svelte": "^2.42.0", + "eslint-plugin-svelte": "^3.5.1", + "eslint-plugin-testing-library": "^7.1.1", + "eslint-plugin-unicorn": "^59.0.1", "expect-type": "^1.2.1", + "globals": "^16.1.0", "happy-dom": "^17.4.6", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -119,6 +120,7 @@ "svelte-check": "^4.1.7", "svelte-jester": "^5.0.0", "typescript": "^5.8.3", + "typescript-eslint": "^8.32.0", "typescript-svelte-plugin": "^0.3.46", "vite": "^6.3.5", "vitest": "^3.1.3" diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..8c5a52d --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,14 @@ +export default { + semi: false, + singleQuote: true, + trailingComma: 'es5', + plugins: ['prettier-plugin-svelte'], + overrides: [ + { + files: '*.svelte', + options: { + parser: 'svelte', + }, + }, + ], +} diff --git a/src/core/index.js b/src/core/index.js index f4a40aa..9e41adf 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -7,10 +7,7 @@ */ import * as LegacyCore from './legacy.js' import * as ModernCore from './modern.svelte.js' -import { - createValidateOptions, - UnknownSvelteOptionsError, -} from './validate-options.js' +import { createValidateOptions } from './validate-options.js' const { mount, unmount, updateProps, allowedOptions } = ModernCore.IS_MODERN_SVELTE ? ModernCore : LegacyCore @@ -18,10 +15,5 @@ const { mount, unmount, updateProps, allowedOptions } = /** Validate component options. */ const validateOptions = createValidateOptions(allowedOptions) -export { - mount, - UnknownSvelteOptionsError, - unmount, - updateProps, - validateOptions, -} +export { mount, unmount, updateProps, validateOptions } +export { UnknownSvelteOptionsError } from './validate-options.js' diff --git a/src/index.js b/src/index.js index 2704824..5a65c08 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,3 @@ -/* eslint-disable import/export */ import { act, cleanup } from './pure.js' // If we're running in a test runner that supports afterEach diff --git a/src/pure.js b/src/pure.js index 875f87c..44e68d6 100644 --- a/src/pure.js +++ b/src/pure.js @@ -65,6 +65,7 @@ const render = (Component, options = {}, renderOptions = {}) => { const queries = getQueriesForElement(baseElement, renderOptions.queries) const target = + // eslint-disable-next-line unicorn/prefer-dom-node-append options.target ?? baseElement.appendChild(document.createElement('div')) targetCache.add(target) @@ -116,14 +117,18 @@ const cleanupTarget = (target) => { const inCache = targetCache.delete(target) if (inCache && target.parentNode === document.body) { - document.body.removeChild(target) + target.remove() } } /** Unmount all components and remove elements added to ``. */ const cleanup = () => { - componentCache.forEach(cleanupComponent) - targetCache.forEach(cleanupTarget) + for (const component of componentCache) { + cleanupComponent(component) + } + for (const target of targetCache) { + cleanupTarget(target) + } } /** @@ -163,12 +168,12 @@ const fireEvent = async (...args) => { return event } -Object.keys(baseFireEvent).forEach((key) => { +for (const [key, baseEvent] of Object.entries(baseFireEvent)) { fireEvent[key] = async (...args) => { - const event = baseFireEvent[key](...args) + const event = baseEvent(...args) await tick() return event } -}) +} export { act, cleanup, fireEvent, render } diff --git a/src/vite.js b/src/vite.js index 1ad712f..b57c886 100644 --- a/src/vite.js +++ b/src/vite.js @@ -1,5 +1,5 @@ -import { dirname, join } from 'node:path' -import { fileURLToPath } from 'node:url' +import path from 'node:path' +import url from 'node:url' /** * Vite plugin to configure @testing-library/svelte. @@ -50,8 +50,8 @@ const addBrowserCondition = (config) => { const browserConditionIndex = conditions.indexOf('browser') if ( - nodeConditionIndex >= 0 && - (nodeConditionIndex < browserConditionIndex || browserConditionIndex < 0) + nodeConditionIndex !== -1 && + (nodeConditionIndex < browserConditionIndex || browserConditionIndex === -1) ) { conditions.splice(nodeConditionIndex, 0, 'browser') } @@ -77,7 +77,9 @@ const addAutoCleanup = (config) => { setupFiles = [setupFiles] } - setupFiles.push(join(dirname(fileURLToPath(import.meta.url)), './vitest.js')) + setupFiles.push( + path.join(path.dirname(url.fileURLToPath(import.meta.url)), './vitest.js') + ) test.setupFiles = setupFiles config.test = test diff --git a/tests/_env.js b/tests/_env.js index f637d6f..96b30f4 100644 --- a/tests/_env.js +++ b/tests/_env.js @@ -1,6 +1,6 @@ import { VERSION as SVELTE_VERSION } from 'svelte/compiler' -export const IS_JSDOM = window.navigator.userAgent.includes('jsdom') +export const IS_JSDOM = globalThis.navigator.userAgent.includes('jsdom') export const IS_HAPPYDOM = !IS_JSDOM // right now it's happy or js diff --git a/tests/act.test.js b/tests/act.test.js index 75c9ded..9308d75 100644 --- a/tests/act.test.js +++ b/tests/act.test.js @@ -1,14 +1,14 @@ import { setTimeout } from 'node:timers/promises' -import { act, render } from '@testing-library/svelte' +import { act, render, screen } from '@testing-library/svelte' import { describe, expect, test } from 'vitest' import Comp from './fixtures/Comp.svelte' describe('act', () => { test('state updates are flushed', async () => { - const { getByText } = render(Comp) - const button = getByText('Button') + render(Comp) + const button = screen.getByText('Button') expect(button).toHaveTextContent('Button') @@ -20,8 +20,8 @@ describe('act', () => { }) test('accepts async functions', async () => { - const { getByText } = render(Comp) - const button = getByText('Button') + render(Comp) + const button = screen.getByText('Button') await act(async () => { await setTimeout(100) diff --git a/tests/context.test.js b/tests/context.test.js index da54b9d..e9d83eb 100644 --- a/tests/context.test.js +++ b/tests/context.test.js @@ -1,4 +1,4 @@ -import { render } from '@testing-library/svelte' +import { render, screen } from '@testing-library/svelte' import { expect, test } from 'vitest' import Comp from './fixtures/Context.svelte' @@ -6,9 +6,9 @@ import Comp from './fixtures/Context.svelte' test('can set a context', () => { const message = 'Got it' - const { getByText } = render(Comp, { + render(Comp, { context: new Map(Object.entries({ foo: { message } })), }) - expect(getByText(message)).toBeTruthy() + expect(screen.getByText(message)).toBeInTheDocument() }) diff --git a/tests/events.test.js b/tests/events.test.js index e0aba4d..9864692 100644 --- a/tests/events.test.js +++ b/tests/events.test.js @@ -1,12 +1,12 @@ -import { fireEvent, render } from '@testing-library/svelte' +import { fireEvent, render, screen } from '@testing-library/svelte' import { describe, expect, test } from 'vitest' import Comp from './fixtures/Comp.svelte' describe('events', () => { test('state changes are flushed after firing an event', async () => { - const { getByText } = render(Comp, { props: { name: 'World' } }) - const button = getByText('Button') + render(Comp, { props: { name: 'World' } }) + const button = screen.getByText('Button') const result = fireEvent.click(button) @@ -15,8 +15,8 @@ describe('events', () => { }) test('calling `fireEvent` directly works too', async () => { - const { getByText } = render(Comp, { props: { name: 'World' } }) - const button = getByText('Button') + render(Comp, { props: { name: 'World' } }) + const button = screen.getByText('Button') const result = fireEvent( button, diff --git a/tests/fixtures/Typed.svelte b/tests/fixtures/Typed.svelte index 383214f..dad8e14 100644 --- a/tests/fixtures/Typed.svelte +++ b/tests/fixtures/Typed.svelte @@ -4,7 +4,7 @@ export let name: string export let count: number - export const hello: string = 'hello' + export const hello = 'hello' const dispatch = createEventDispatcher<{ greeting: string }>() diff --git a/tests/fixtures/TypedRunes.svelte b/tests/fixtures/TypedRunes.svelte index 979be41..0fb690b 100644 --- a/tests/fixtures/TypedRunes.svelte +++ b/tests/fixtures/TypedRunes.svelte @@ -1,7 +1,7 @@

hello {name}

diff --git a/tests/render.test.js b/tests/render.test.js index 943c920..19bf2c3 100644 --- a/tests/render.test.js +++ b/tests/render.test.js @@ -1,4 +1,4 @@ -import { render } from '@testing-library/svelte' +import { render, screen } from '@testing-library/svelte' import { beforeAll, describe, expect, test } from 'vitest' import { COMPONENT_FIXTURES } from './_env.js' @@ -12,14 +12,14 @@ describe.each(COMPONENT_FIXTURES)('render ($mode)', ({ component }) => { }) test('renders component into the document', () => { - const { getByText } = render(Comp, { props }) + render(Comp, { props }) - expect(getByText('Hello World!')).toBeInTheDocument() + expect(screen.getByText('Hello World!')).toBeInTheDocument() }) test('accepts props directly', () => { - const { getByText } = render(Comp, props) - expect(getByText('Hello World!')).toBeInTheDocument() + render(Comp, props) + expect(screen.getByText('Hello World!')).toBeInTheDocument() }) test('throws error when mixing svelte component options and props', () => { @@ -35,8 +35,8 @@ describe.each(COMPONENT_FIXTURES)('render ($mode)', ({ component }) => { }) test('should return a container object wrapping the DOM of the rendered component', () => { - const { container, getByTestId } = render(Comp, props) - const firstElement = getByTestId('test') + const { container } = render(Comp, props) + const firstElement = screen.getByTestId('test') expect(container.firstChild).toBe(firstElement) }) @@ -73,17 +73,14 @@ describe.each(COMPONENT_FIXTURES)('render ($mode)', ({ component }) => { const baseElement = document.body const target = document.createElement('section') const anchor = document.createElement('div') - baseElement.appendChild(target) - target.appendChild(anchor) + baseElement.append(target) + target.append(anchor) - const { getByTestId } = render( - Comp, - { props, target, anchor }, - { baseElement } - ) - const firstElement = getByTestId('test') + render(Comp, { props, target, anchor }, { baseElement }) + const firstElement = screen.getByTestId('test') expect(target.firstChild).toBe(firstElement) + // eslint-disable-next-line testing-library/no-node-access expect(target.lastChild).toBe(anchor) }) }) diff --git a/tests/rerender.test.js b/tests/rerender.test.js index 2a20143..2fdff0d 100644 --- a/tests/rerender.test.js +++ b/tests/rerender.test.js @@ -39,8 +39,8 @@ describe.each(COMPONENT_FIXTURES)('rerender ($mode)', ({ mode, component }) => { ? { name: 'World' } : { accessors: true, props: { name: 'World' } } - const { component, getByText } = render(Comp, componentOptions) - const element = getByText('Hello World!') + const { component } = render(Comp, componentOptions) + const element = screen.getByText('Hello World!') expect(element).toBeInTheDocument() expect(component.name).toBe('World') diff --git a/tests/transition.test.js b/tests/transition.test.js index 3acf1d0..27b236e 100644 --- a/tests/transition.test.js +++ b/tests/transition.test.js @@ -6,12 +6,13 @@ import { IS_JSDOM, IS_SVELTE_5 } from './_env.js' import Transitioner from './fixtures/Transitioner.svelte' describe.skipIf(IS_SVELTE_5)('transitions', () => { - beforeEach(() => { - if (!IS_JSDOM) return - - const raf = (fn) => setTimeout(() => fn(new Date()), 16) - vi.stubGlobal('requestAnimationFrame', raf) - }) + if (IS_JSDOM) { + beforeEach(() => { + vi.stubGlobal('requestAnimationFrame', (fn) => + setTimeout(() => fn(new Date()), 16) + ) + }) + } test('on:introend', async () => { const user = userEvent.setup()