diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e76d9d7cf..69981f192 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 2 - uses: pnpm/action-setup@v2 with: - version: 8.4.0 + version: 9.15.2 run_install: false - name: Get pnpm store directory id: pnpm-cache diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6aee4d6e7..4a7ffae08 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v3 - uses: pnpm/action-setup@v2 with: - version: 8.4.0 + version: 9.15.2 run_install: false - name: Get pnpm store directory id: pnpm-cache diff --git a/packages/casl-ability/src/matchers/conditions.ts b/packages/casl-ability/src/matchers/conditions.ts index 235ef41b1..541d56cab 100644 --- a/packages/casl-ability/src/matchers/conditions.ts +++ b/packages/casl-ability/src/matchers/conditions.ts @@ -1,38 +1,38 @@ import { + $all, + $elemMatch, $eq, - eq, - $ne, - ne, - $lt, - lt, - $lte, - lte, + $exists, $gt, - gt, $gte, - gte, $in, - within, + $lt, + $lte, + $ne, $nin, - nin, - $all, - all, - $size, - size, - $regex, $options, - regex, - $elemMatch, - elemMatch, - $exists, - exists, + $regex, + $size, + all, and, - createFactory, BuildMongoQuery, + createFactory, DefaultOperators, + elemMatch, + eq, + exists, + gt, + gte, + lt, + lte, + ne, + nin, + regex, + size, + within } from '@ucast/mongo2js'; -import { ConditionsMatcher, AnyObject } from '../types'; import { Container, GenericFactory } from '../hkt'; +import { AnyObject, ConditionsMatcher } from '../types'; const defaultInstructions = { $eq, @@ -87,7 +87,6 @@ export const buildMongoQueryMatcher = ((instructions, interpreters, options) => export const mongoQueryMatcher = createFactory(defaultInstructions, defaultInterpreters); export type { - MongoQueryFieldOperators, - MongoQueryTopLevelOperators, - MongoQueryOperators, + MongoQueryFieldOperators, MongoQueryOperators, MongoQueryTopLevelOperators } from '@ucast/mongo2js'; + diff --git a/packages/casl-react/package.json b/packages/casl-react/package.json index f291da835..26eb55163 100644 --- a/packages/casl-react/package.json +++ b/packages/casl-react/package.json @@ -27,7 +27,7 @@ "build": "npm run build.prepare && BUILD_TYPES=es5m,es6m,umd dx rollup -n casl.react -g react:React,prop-types:React.PropTypes,@casl/ability:casl", "build.types": "dx tsc", "lint": "dx eslint src/ spec/", - "test": "dx jest --env jsdom --config ../dx/config/jest.chai.config.js", + "test": "dx jest --env jsdom", "release.prepare": "npm run lint && npm test && NODE_ENV=production npm run build", "release": "npm run release.prepare && dx semantic-release" }, @@ -41,20 +41,21 @@ "author": "Sergii Stotskyi ", "license": "MIT", "peerDependencies": { - "@casl/ability": "^3.0.0 || ^4.0.0 || ^5.1.0 || ^6.0.0", - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + "@casl/ability": "^4.0.0 || ^5.1.0 || ^6.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "devDependencies": { "@casl/ability": "^6.0.0", "@casl/dx": "workspace:^1.0.0", - "@testing-library/react-hooks": "^4.0.1", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.1.0", "@types/jest": "^29.0.0", "@types/node": "^22.0.0", - "@types/react": "^18.0.0", + "@types/react": "^19.0.0", "chai": "^4.1.0", "chai-spies": "^1.0.0", - "react": "^18.0.0", - "react-test-renderer": "^18.0.0" + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "files": [ "dist", diff --git a/packages/casl-react/spec/Can.spec.js b/packages/casl-react/spec/Can.spec.js deleted file mode 100644 index 8f8bf8a01..000000000 --- a/packages/casl-react/spec/Can.spec.js +++ /dev/null @@ -1,121 +0,0 @@ -import { createElement as e } from 'react' -import { defineAbility } from '@casl/ability' -import renderer from 'react-test-renderer' -import { Can } from '../src' - -describe('`Can` component', () => { - let ability - let children - - beforeEach(() => { - children = spy(() => null) - ability = defineAbility(can => can('read', 'Post')) - }) - - it('passes ability check value and instance as arguments to "children" function', () => { - renderer.create(e(Can, { I: 'read', a: 'Post', ability }, children)) - - expect(children).to.have.been.called.with.exactly(ability.can('read', 'Post'), ability) - }) - - it('has public "allowed" property which returns boolean indicating whether children will be rendered', () => { - const canComponent = renderer.create(e(Can, { I: 'read', a: 'Post', ability }, children)) - renderer.create(e(Can, { not: true, I: 'run', a: 'Marathon', ability }, children)) - - expect(canComponent.getInstance().allowed).to.equal(ability.can('read', 'Post')) - expect(canComponent.getInstance().allowed).to.equal(ability.cannot('run', 'Marathon')) - }) - - it('unsubscribes from ability updates when unmounted', () => { - const component = renderer.create(e(Can, { I: 'read', a: 'Post', ability }, children)) - - spy.on(ability, 'can') - component.unmount() - ability.update([]) - - expect(ability.can).not.to.have.been.called() - }) - - describe('#render', () => { - let child - - beforeEach(() => { - child = e('a', null, 'children') - }) - - it('renders children if ability allows to perform an action', () => { - const component = renderer.create(e(Can, { I: 'read', a: 'Post', ability }, child)) - - expect(component.toJSON().children).to.deep.equal([child.props.children]) - }) - - it('does not render children if ability does not allow to perform an action', () => { - const component = renderer.create(e(Can, { I: 'update', a: 'Post', ability }, child)) - - expect(component.toJSON()).to.be.null - }) - - it('does not render children if ability allows to perform an action, but `not` is set to true', () => { - const component = renderer.create(e(Can, { not: true, I: 'read', a: 'Post', ability }, child)) - - expect(component.toJSON()).to.be.null - }) - - it('rerenders when ability rules are changed', () => { - const component = renderer.create(e(Can, { I: 'read', a: 'Post', ability }, child)) - ability.update([]) - - expect(component.toJSON()).to.be.null - }) - - it('rerenders when `I` prop is changed', () => { - const component = renderer.create(e(Can, { I: 'update', a: 'Post', ability }, child)) - component.update(e(Can, { I: 'read', a: 'Post', ability }, child)) - - expect(component.toJSON().children).to.deep.equal([child.props.children]) - }) - - it('rerenders when `a` or `an` or `this` prop is changed', () => { - const component = renderer.create(e(Can, { I: 'read', a: 'User', ability }, child)) - component.update(e(Can, { I: 'read', a: 'Post', ability }, child)) - - expect(component.toJSON().children).to.deep.equal([child.props.children]) - }) - - it('rerenders when `not` prop is changed', () => { - const component = renderer.create(e(Can, { not: true, I: 'read', a: 'Post', ability }, child)) - component.update(e(Can, { not: false, I: 'read', a: 'Post', ability }, child)) - - expect(component.toJSON().children).to.deep.equal([child.props.children]) - }) - - it('does not rerender itself when previous ability rules are changed', () => { - const component = renderer.create(e(Can, { I: 'read', a: 'Post', ability }, child)) - const anotherAbility = defineAbility(can => can('manage', 'Post')) - - component.update(e(Can, { I: 'read', a: 'Post', ability: anotherAbility }, child)) - ability.update([]) - - expect(component.toJSON().children).to.deep.equal([child.props.children]) - }) - - it('can render multiple children if `React.Fragment` is available', () => { - const localChildren = [child, e('h1', null, 'another children')] - const component = renderer.create( - e(Can, { I: 'read', a: 'Post', ability }, ...localChildren) - ) - const renderedChildren = localChildren.map(element => renderer.create(element).toJSON()) - - expect(component.toJSON()).to.deep.equal(renderedChildren) - }) - - it('always renders children if `passThrough` prop is `true`', () => { - const component = renderer.create( - e(Can, { I: 'delete', a: 'Post', passThrough: true, ability }, child) - ) - - expect(ability.can('delete', 'Post')).to.be.false - expect(component.toJSON().children).to.deep.equal([child.props.children]) - }) - }) -}) diff --git a/packages/casl-react/spec/Can.spec.tsx b/packages/casl-react/spec/Can.spec.tsx new file mode 100644 index 000000000..bbe09a28e --- /dev/null +++ b/packages/casl-react/spec/Can.spec.tsx @@ -0,0 +1,111 @@ +import React from 'react' +import { defineAbility, MongoAbility } from '@casl/ability' +import { Can } from '../src' +import { act, render, screen } from '@testing-library/react' + +describe('`Can` component', () => { + let ability: MongoAbility + + beforeEach(() => { + ability = defineAbility(can => can('read', 'Post')) + }) + + it('passes ability check value and instance as arguments to "children" function', () => { + const children = jest.fn() + render({children}) + + expect(children).toHaveBeenCalledWith(ability.can('read', 'Post'), ability) + }) + + it('unsubscribes from ability updates when unmounted', () => { + jest.spyOn(ability, 'can') + const component = render(test) + + component.unmount() + act(() => ability.update([])) + + expect(ability.can).toHaveBeenCalledTimes(1) + }) + + describe('rendering', () => { + it('renders children if ability allows to perform an action', () => { + render(I can see it) + + expect(screen.queryByText('I can see it')).toBeTruthy() + }) + + it('does not render children if ability does not allow to perform an action', () => { + render(I can see it) + + expect(screen.queryByText('I can see it')).not.toBeTruthy() + }) + + it('does not render children if ability allows to perform an action, but `not` is set to true', () => { + render(I can see it) + + expect(screen.queryByText('I can see it')).not.toBeTruthy() + }) + + it('rerenders when ability rules are changed', () => { + render(I can see it) + expect(screen.queryByText('I can see it')).toBeTruthy() + + act(() => ability.update([])) + expect(screen.findByText('I can see it')).toBeTruthy() + }) + + it('rerenders when `I` prop is changed', () => { + const component = render(I can see it) + expect(screen.queryByText('I can see it')).not.toBeTruthy() + + component.rerender(I can see it) + expect(screen.queryByText('I can see it')).toBeTruthy() + }) + + it('rerenders when `a` or `an` or `this` prop is changed', () => { + const component = render(I can see it) + expect(screen.queryByText('I can see it')).not.toBeTruthy() + + component.rerender(I can see it) + expect(screen.queryByText('I can see it')).toBeTruthy() + }) + + it('rerenders when `not` prop is changed', () => { + const component = render(I can see it) + expect(screen.queryByText('I can see it')).not.toBeTruthy() + + component.rerender(I can see it) + expect(screen.queryByText('I can see it')).toBeTruthy() + }) + + it('does not rerender itself when previous ability rules are changed', () => { + const component = render(I can see it) + const anotherAbility = defineAbility(can => can('manage', 'Post')) + + jest.spyOn(ability, 'can') + component.rerender(I can see it) + act(() => ability.update([])) + + expect(screen.queryByText('I can see it')).toBeTruthy() + expect(ability.can).not.toHaveBeenCalled() + }) + + it('can render multiple children with `React.Fragment`', () => { + render(<> +

line 1

+

line 2

+
) + + expect(screen.queryByText('line 1')).toBeTruthy() + expect(screen.queryByText('line 2')).toBeTruthy() + }) + + it('always renders children if `passThrough` prop is `true`', () => { + const children = jest.fn() + render({children}) + + expect(ability.can('delete', 'Post')).toBe(false) + expect(children).toHaveBeenCalledWith(false, ability) + }) + }) +}) diff --git a/packages/casl-react/spec/factory.spec.js b/packages/casl-react/spec/factory.spec.js deleted file mode 100644 index 67a0449e4..000000000 --- a/packages/casl-react/spec/factory.spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import { createElement as e, createContext } from 'react' -import renderer from 'react-test-renderer' -import { defineAbility } from '@casl/ability' -import { Can, createCanBoundTo, createContextualCan } from '../src' - -describe('Factory methods which create `Can` component', () => { - let ability - let child - - beforeEach(() => { - ability = defineAbility(can => can('read', 'Post')) - child = spy(() => e('p', null, 'children')) - }) - - describe('`createCanBoundTo`', () => { - let BoundCan - - beforeEach(() => { - BoundCan = createCanBoundTo(ability) - }) - - it('creates another component with bound ability instance', () => { - const component = renderer.create(e(BoundCan, { I: 'read', a: 'Post' }, child)) - - expect(component.toJSON().children).to.deep.equal([child().props.children]) - }) - - it('extends `Can` component', () => { - const component = renderer.create(e(BoundCan, { I: 'read', a: 'Post' }, child)) - const instance = component.getInstance() - - expect(instance).to.be.instanceof(Can) - expect(instance).to.be.instanceof(BoundCan) - }) - - it('allows to override ability by passing "ability" property', () => { - const anotherAbility = defineAbility(can => can('update', 'Post')) - const component = renderer.create(e(BoundCan, { I: 'read', a: 'Post', ability: anotherAbility }, child)) - - expect(component.toJSON()).to.be.null - }) - }) - - describe('`createContextualCan`', () => { - let AbilityContext - let ContextualCan - - beforeEach(() => { - AbilityContext = createContext() - ContextualCan = createContextualCan(AbilityContext.Consumer) - }) - - it('allows to override `Ability` instance by passing it in props', () => { - const element = e(ContextualCan, { I: 'read', a: 'Post', ability }, child) - renderer.create(element) - - expect(child).to.have.been.called.with(ability) - }) - - it('expects `Ability` instance to be provided by context Provider', () => { - const App = e( - AbilityContext.Provider, - { value: ability }, - e(ContextualCan, { I: 'read', a: 'Post' }, child) - ) - renderer.create(App) - - expect(child).to.have.been.called.with(ability) - }) - }) -}) diff --git a/packages/casl-react/spec/factory.spec.tsx b/packages/casl-react/spec/factory.spec.tsx new file mode 100644 index 000000000..1eb198765 --- /dev/null +++ b/packages/casl-react/spec/factory.spec.tsx @@ -0,0 +1,35 @@ +import { createMongoAbility, defineAbility, MongoAbility } from '@casl/ability' +import React, { createContext } from 'react' +import { render, screen } from '@testing-library/react' +import { BoundCanProps, createContextualCan } from '../src' + +describe('`createContextualCan`', () => { + let ability: MongoAbility + let AbilityContext: React.Context + let ContextualCan: React.FunctionComponent> + + beforeEach(() => { + ability = defineAbility(can => can('read', 'Post')) + AbilityContext = createContext(createMongoAbility()) + ContextualCan = createContextualCan(AbilityContext.Consumer) + }) + + it('allows to override `Ability` instance by passing it in props', () => { + render(I see it) + + expect(screen.queryByText('I see it')).toBeTruthy() + }) + + it('expects `Ability` instance to be provided by context Provider', () => { + render( + I see it + ) + + expect(screen.queryByText('I see it')).toBeTruthy() + }) + + it('should not render anything if ability does not have rules', () => { + render(I see it) + expect(screen.queryByText('I see it')).not.toBeTruthy() + }) +}) diff --git a/packages/casl-react/spec/useAbility.spec.js b/packages/casl-react/spec/useAbility.spec.ts similarity index 61% rename from packages/casl-react/spec/useAbility.spec.js rename to packages/casl-react/spec/useAbility.spec.ts index b939ff50e..e5c02fe9c 100644 --- a/packages/casl-react/spec/useAbility.spec.js +++ b/packages/casl-react/spec/useAbility.spec.ts @@ -1,35 +1,35 @@ +import { createMongoAbility, MongoAbility } from '@casl/ability' +import { act, renderHook } from '@testing-library/react' import { createContext } from 'react' -import { renderHook, act } from '@testing-library/react-hooks' -import { Ability } from '@casl/ability' import { useAbility } from '../src' describe('`useAbility` hook', () => { - let ability - let AbilityContext + let ability: MongoAbility + let AbilityContext: React.Context beforeEach(() => { - ability = new Ability() + ability = createMongoAbility() AbilityContext = createContext(ability) }) it('provides an `Ability` instance from context', () => { const { result } = renderHook(() => useAbility(AbilityContext)) - expect(result.current).to.equal(ability) + expect(result.current).toBe(ability) }) it('triggers re-render when `Ability` rules are changed', () => { - const component = spy(() => useAbility(AbilityContext)) + const component = jest.fn(() => useAbility(AbilityContext)) renderHook(component) act(() => { ability.update([{ action: 'read', subject: 'Post' }]) }) - expect(component).to.have.been.called.exactly(2) + expect(component).toHaveBeenCalledTimes(2) }) it('subscribes to `Ability` instance only once', () => { - spy.on(ability, 'on') + jest.spyOn(ability, 'on') const { rerender } = renderHook(() => useAbility(AbilityContext)) act(() => { @@ -37,11 +37,11 @@ describe('`useAbility` hook', () => { rerender() }) - expect(ability.on).to.have.been.called.once + expect(ability.on).toHaveBeenCalledTimes(1) }) it('unsubscribes from `Ability` when component is destroyed', () => { - const component = spy(() => useAbility(AbilityContext)) + const component = jest.fn(() => useAbility(AbilityContext)) const { unmount } = renderHook(component) act(() => { @@ -49,6 +49,6 @@ describe('`useAbility` hook', () => { ability.update([{ action: 'read', subject: 'Post' }]) }) - expect(component).to.have.been.called.once + expect(component).toHaveBeenCalledTimes(1) }) }) diff --git a/packages/casl-react/src/Can.ts b/packages/casl-react/src/Can.ts index db0011fa3..9ced77876 100644 --- a/packages/casl-react/src/Can.ts +++ b/packages/casl-react/src/Can.ts @@ -41,13 +41,11 @@ export type CanProps = export type BoundCanProps = AbilityCanProps['abilities']> & BoundCanExtraProps; -export class Can< - T extends AnyAbility, - IsBound extends boolean = false -> extends PureComponent : CanProps> { +export class Can extends PureComponent, { t: boolean }> { private _isAllowed = false; private _ability: T | null = null; private _unsubscribeFromAbility: Unsubscribe = noop; + state = { t: true } componentWillUnmount() { this._unsubscribeFromAbility(); @@ -63,7 +61,7 @@ export class Can< if (ability) { this._ability = ability; - this._unsubscribeFromAbility = ability.on('updated', () => this.forceUpdate()); + this._unsubscribeFromAbility = ability.on('updated', () => this.setState({ t: !this.state.t })); } } diff --git a/packages/casl-react/src/factory.ts b/packages/casl-react/src/factory.ts index 0a62d1cdc..e7d92173d 100644 --- a/packages/casl-react/src/factory.ts +++ b/packages/casl-react/src/factory.ts @@ -1,24 +1,12 @@ -import { createElement as h, ComponentClass, Consumer, FunctionComponent } from 'react'; import { AnyAbility } from '@casl/ability'; -import { Can, BoundCanProps } from './Can'; - -interface BoundCanClass extends ComponentClass> { - new (props: BoundCanProps, context?: any): Can -} - -export function createCanBoundTo(ability: T): BoundCanClass { - return class extends Can { - static defaultProps = { ability } as BoundCanClass['defaultProps']; - }; -} +import { Consumer, FunctionComponent, createElement } from 'react'; +import { BoundCanProps, Can } from './Can'; export function createContextualCan( Getter: Consumer ): FunctionComponent> { - return (props: BoundCanProps) => h(Getter, { - children: (ability: T) => h(Can, { - ability, - ...props, - } as any) + return (props: BoundCanProps) => createElement(Getter, { + children: (ability: T) => + createElement(Can, { ...props, ability: props.ability || ability } as any), }); } diff --git a/packages/casl-react/src/hooks/index.ts b/packages/casl-react/src/hooks/index.ts deleted file mode 100644 index e2e106965..000000000 --- a/packages/casl-react/src/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useAbility'; diff --git a/packages/casl-react/src/hooks/useAbility.ts b/packages/casl-react/src/hooks/useAbility.ts index dd95590c1..6f9647685 100644 --- a/packages/casl-react/src/hooks/useAbility.ts +++ b/packages/casl-react/src/hooks/useAbility.ts @@ -2,11 +2,6 @@ import React from 'react'; import { AnyAbility } from '@casl/ability'; export function useAbility(context: React.Context): T { - if (process.env.NODE_ENV !== 'production' && typeof React.useContext !== 'function') { - /* istanbul ignore next */ - throw new Error('You must use React >= 16.8 in order to use useAbility()'); - } - const ability = React.useContext(context); const [rules, setRules] = React.useState(); diff --git a/packages/casl-react/src/index.ts b/packages/casl-react/src/index.ts index 5ea538185..6dda3bdb4 100644 --- a/packages/casl-react/src/index.ts +++ b/packages/casl-react/src/index.ts @@ -1,3 +1,3 @@ export * from './Can'; export * from './factory'; -export * from './hooks'; +export * from './hooks/useAbility'; diff --git a/packages/casl-react/tsconfig.build.json b/packages/casl-react/tsconfig.build.json index ded973988..8a4a95690 100644 --- a/packages/casl-react/tsconfig.build.json +++ b/packages/casl-react/tsconfig.build.json @@ -1,9 +1,14 @@ { - "extends": "./tsconfig", + "extends": "../../tsconfig", + "include": [ + "src/*", + "spec/*" + ], "exclude": [ "spec/**/*" ], "compilerOptions": { - "outDir": "dist/types" + "outDir": "dist/types", + "allowSyntheticDefaultImports": true } } diff --git a/packages/casl-react/tsconfig.json b/packages/casl-react/tsconfig.json index ef714ac0d..92b900618 100644 --- a/packages/casl-react/tsconfig.json +++ b/packages/casl-react/tsconfig.json @@ -5,6 +5,7 @@ "spec/*" ], "compilerOptions": { + "jsx": "react", "outDir": "dist/types", "allowSyntheticDefaultImports": true } diff --git a/packages/dx/config/jest.config.js b/packages/dx/config/jest.config.js index 8f978266a..7d4aa11af 100644 --- a/packages/dx/config/jest.config.js +++ b/packages/dx/config/jest.config.js @@ -10,7 +10,7 @@ module.exports = { '/src/**/*.{ts,js}' ], testMatch: [ - '/spec/**/*.spec.{ts,js}' + '/spec/**/*.spec.{ts,tsx,js}' ], transform: { '^.+\\.[t|j]sx?$': 'ts-jest', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d593d0c31..dc4950f63 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,9 +178,12 @@ importers: '@casl/dx': specifier: workspace:^1.0.0 version: link:../dx - '@testing-library/react-hooks': - specifier: ^4.0.1 - version: 4.0.1(react-test-renderer@18.3.1(react@18.3.1))(react@18.3.1) + '@testing-library/dom': + specifier: ^10.4.0 + version: 10.4.0 + '@testing-library/react': + specifier: ^16.1.0 + version: 16.1.0(@testing-library/dom@10.4.0)(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/jest': specifier: ^29.0.0 version: 29.5.12 @@ -188,8 +191,8 @@ importers: specifier: ^22.0.0 version: 22.5.4 '@types/react': - specifier: ^18.0.0 - version: 18.3.5 + specifier: ^19.0.0 + version: 19.0.2 chai: specifier: ^4.1.0 version: 4.5.0 @@ -197,11 +200,11 @@ importers: specifier: ^1.0.0 version: 1.1.0(chai@4.5.0) react: - specifier: ^18.0.0 - version: 18.3.1 - react-test-renderer: - specifier: ^18.0.0 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) packages/casl-vue: devDependencies: @@ -2215,11 +2218,24 @@ packages: peerDependencies: eslint: '>=8.40.0' - '@testing-library/react-hooks@4.0.1': - resolution: {integrity: sha512-DufI8Q2GOM7W2yFEEfz85VNVNaHZL0tPZyBT6ytV7HK+1A4frL1ty+W5NBE0u0K3EFV/Pg5O28HGNEtp9D5EyA==} + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + + '@testing-library/react@16.1.0': + resolution: {integrity: sha512-Q2ToPvg0KsVL0ohND9A3zLJWcOXXcO8IDu3fj11KhNt0UlCWyFyvnCIBkd12tidB2lkiVRG8VFqdhcqhqnAQtg==} + engines: {node: '>=18'} peerDependencies: - react: '>=16.9.0' - react-test-renderer: '>=16.9.0' + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true '@tootallnate/once@1.1.2': resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} @@ -2229,6 +2245,9 @@ packages: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2325,20 +2344,14 @@ packages: '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/prop-types@15.7.12': - resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - '@types/qs@6.9.15': resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - '@types/react-test-renderer@18.3.0': - resolution: {integrity: sha512-HW4MuEYxfDbOHQsVlY/XtOvNHftCVEPhJF2pQXXwcUiUF+Oyb0usgp48HSgpK5rt8m9KZb22yqOeZm+rrVG8gw==} - - '@types/react@18.3.5': - resolution: {integrity: sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==} + '@types/react@19.0.2': + resolution: {integrity: sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -2683,6 +2696,9 @@ packages: engines: {node: '>=0.6.10'} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} @@ -3416,6 +3432,10 @@ packages: deprecation@2.3.1: resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -3447,6 +3467,9 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -4808,10 +4831,6 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} @@ -4825,6 +4844,10 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} @@ -5215,10 +5238,6 @@ packages: nwsapi@2.2.12: resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - object-inspect@1.13.2: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} @@ -5539,6 +5558,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5612,21 +5635,19 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - react-shallow-renderer@16.15.0: - resolution: {integrity: sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==} + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react: ^19.0.0 - react-test-renderer@18.3.1: - resolution: {integrity: sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==} - peerDependencies: - react: ^18.3.1 + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} read-pkg-up@7.0.1: @@ -5841,8 +5862,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} schema-utils@3.3.0: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} @@ -9020,18 +9041,32 @@ snapshots: - supports-color - typescript - '@testing-library/react-hooks@4.0.1(react-test-renderer@18.3.1(react@18.3.1))(react@18.3.1)': + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/runtime': 7.25.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/react@16.1.0(@testing-library/dom@10.4.0)(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.25.6 - '@types/react': 18.3.5 - '@types/react-test-renderer': 18.3.0 - react: 18.3.1 - react-test-renderer: 18.3.1(react@18.3.1) + '@testing-library/dom': 10.4.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.2 '@tootallnate/once@1.1.2': {} '@tootallnate/once@2.0.0': {} + '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.25.6 @@ -9150,19 +9185,12 @@ snapshots: '@types/parse-json@4.0.2': {} - '@types/prop-types@15.7.12': {} - '@types/qs@6.9.15': {} '@types/range-parser@1.2.7': {} - '@types/react-test-renderer@18.3.0': - dependencies: - '@types/react': 18.3.5 - - '@types/react@18.3.5': + '@types/react@19.0.2': dependencies: - '@types/prop-types': 15.7.12 csstype: 3.1.3 '@types/resolve@1.20.2': {} @@ -9578,6 +9606,10 @@ snapshots: argv@0.0.2: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 @@ -10515,6 +10547,8 @@ snapshots: deprecation@2.3.1: {} + dequal@2.0.3: {} + destroy@1.2.0: {} detect-libc@2.0.3: {} @@ -10537,6 +10571,8 @@ snapshots: dependencies: esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -11848,7 +11884,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -12309,10 +12345,6 @@ snapshots: strip-ansi: 7.1.0 wrap-ansi: 9.0.0 - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - loupe@2.3.7: dependencies: get-func-name: 2.0.2 @@ -12327,6 +12359,8 @@ snapshots: dependencies: yallist: 4.0.0 + lz-string@1.5.0: {} + magic-string@0.30.11: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -12613,8 +12647,6 @@ snapshots: nwsapi@2.2.12: {} - object-assign@4.1.1: {} - object-inspect@1.13.2: {} object-keys@1.1.1: {} @@ -12775,7 +12807,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -12914,6 +12946,12 @@ snapshots: prelude-ls@1.2.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -12981,24 +13019,16 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-is@18.3.1: {} - - react-shallow-renderer@16.15.0(react@18.3.1): + react-dom@19.0.0(react@19.0.0): dependencies: - object-assign: 4.1.1 - react: 18.3.1 - react-is: 18.3.1 + react: 19.0.0 + scheduler: 0.25.0 - react-test-renderer@18.3.1(react@18.3.1): - dependencies: - react: 18.3.1 - react-is: 18.3.1 - react-shallow-renderer: 16.15.0(react@18.3.1) - scheduler: 0.23.2 + react-is@17.0.2: {} - react@18.3.1: - dependencies: - loose-envify: 1.4.0 + react-is@18.3.1: {} + + react@19.0.0: {} read-pkg-up@7.0.1: dependencies: @@ -13244,9 +13274,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.25.0: {} schema-utils@3.3.0: dependencies: