diff --git a/packages/casl-react/package.json b/packages/casl-react/package.json index 9b3d44bdf..7787d1e97 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" }, @@ -42,19 +42,20 @@ "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 || ^19.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": "^8.0.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.1.0", "@types/jest": "^29.0.0", "@types/node": "^22.0.0", "@types/react": "^19.0.0", "chai": "^4.1.0", "chai-spies": "^1.0.0", "react": "^19.0.0", - "react-test-renderer": "^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.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 ca9f58a90..dc4950f63 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,7 +74,7 @@ importers: version: 29.7.0(@types/node@22.5.4) jest-preset-angular: specifier: ^14.0.0 - version: 14.2.2(bsglqkf6e2rmcewhkdd34vcbdq) + version: 14.2.2(j5zdhdj52hmp5rbbehkbf6xb4m) rxjs: specifier: ^7.5.5 version: 7.8.1 @@ -178,9 +178,12 @@ importers: '@casl/dx': specifier: workspace:^1.0.0 version: link:../dx - '@testing-library/react-hooks': - specifier: ^8.0.0 - version: 8.0.1(@types/react@19.0.2)(react-test-renderer@19.0.0(react@19.0.0))(react@19.0.0) + '@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 @@ -199,7 +202,7 @@ importers: react: specifier: ^19.0.0 version: 19.0.0 - react-test-renderer: + react-dom: specifier: ^19.0.0 version: 19.0.0(react@19.0.0) @@ -285,7 +288,7 @@ importers: version: 17.4.7 ts-jest: specifier: ^29.0.0 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.5.4))(typescript@5.5.4) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.5.4))(typescript@5.5.4) typescript: specifier: ~5.5.0 version: 5.5.4 @@ -2215,20 +2218,23 @@ packages: peerDependencies: eslint: '>=8.40.0' - '@testing-library/react-hooks@8.0.1': - resolution: {integrity: sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==} - engines: {node: '>=12'} + '@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: - '@types/react': ^16.9.0 || ^17.0.0 - react: ^16.9.0 || ^17.0.0 - react-dom: ^16.9.0 || ^17.0.0 - react-test-renderer: ^16.9.0 || ^17.0.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 - react-dom: - optional: true - react-test-renderer: + '@types/react-dom': optional: true '@tootallnate/once@1.1.2': @@ -2239,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==} @@ -2687,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'} @@ -3420,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} @@ -3451,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==} @@ -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==} @@ -5535,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} @@ -5608,23 +5635,17 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-error-boundary@3.1.4: - resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} - engines: {node: '>=10', npm: '>=6'} + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: - react: '>=16.13.1' + react: ^19.0.0 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-is@19.0.0: - resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} - - react-test-renderer@19.0.0: - resolution: {integrity: sha512-oX5u9rOQlHzqrE/64CNr0HB0uWxkCQmZNSfozlYvwE71TLVgeZxVf0IjouGEr1v7r1kcDifdAJBeOhdhxsG/DA==} - peerDependencies: - react: ^19.0.0 - react@19.0.0: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} @@ -7265,21 +7286,45 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7305,16 +7350,34 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-import-attributes@7.25.6(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7325,41 +7388,89 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-typescript@7.25.4(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -8930,19 +9041,32 @@ snapshots: - supports-color - typescript - '@testing-library/react-hooks@8.0.1(@types/react@19.0.2)(react-test-renderer@19.0.0(react@19.0.0))(react@19.0.0)': + '@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 + '@testing-library/dom': 10.4.0 react: 19.0.0 - react-error-boundary: 3.1.4(react@19.0.0) + react-dom: 19.0.0(react@19.0.0) optionalDependencies: '@types/react': 19.0.2 - react-test-renderer: 19.0.0(react@19.0.0) '@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 @@ -9482,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 @@ -9735,6 +9863,20 @@ snapshots: transitivePeerDependencies: - supports-color + babel-jest@29.7.0(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.26.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + optional: true + babel-loader@9.1.3(@babel/core@7.25.2)(webpack@5.94.0(esbuild@0.23.0)): dependencies: '@babel/core': 7.25.2 @@ -9802,12 +9944,39 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.25.2) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.25.2) + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.0) + '@babel/plugin-syntax-import-attributes': 7.25.6(@babel/core@7.26.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) + optional: true + babel-preset-jest@29.6.3(@babel/core@7.25.2): dependencies: '@babel/core': 7.25.2 babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.1.0(@babel/core@7.25.2) + babel-preset-jest@29.6.3(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + optional: true + balanced-match@1.0.2: {} base64-js@1.5.1: {} @@ -10378,6 +10547,8 @@ snapshots: deprecation@2.3.1: {} + dequal@2.0.3: {} + destroy@1.2.0: {} detect-libc@2.0.3: {} @@ -10400,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 @@ -11711,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 @@ -11731,7 +11904,7 @@ snapshots: optionalDependencies: jest-resolve: 29.7.0 - jest-preset-angular@14.2.2(bsglqkf6e2rmcewhkdd34vcbdq): + jest-preset-angular@14.2.2(j5zdhdj52hmp5rbbehkbf6xb4m): dependencies: '@angular-devkit/build-angular': 18.2.3(@angular/compiler-cli@19.0.5(@angular/compiler@19.0.5(@angular/core@19.0.5(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.4))(@types/node@22.5.4)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.5.4))(typescript@5.5.4) '@angular/compiler-cli': 19.0.5(@angular/compiler@19.0.5(@angular/core@19.0.5(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.4) @@ -11743,7 +11916,7 @@ snapshots: jest-environment-jsdom: 29.7.0 jest-util: 29.7.0 pretty-format: 29.7.0 - ts-jest: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.5.4))(typescript@5.5.4) + ts-jest: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.5.4))(typescript@5.5.4) typescript: 5.5.4 optionalDependencies: esbuild: 0.23.1 @@ -12186,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 @@ -12632,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 @@ -12771,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 @@ -12838,20 +13019,14 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-error-boundary@3.1.4(react@19.0.0): + react-dom@19.0.0(react@19.0.0): dependencies: - '@babel/runtime': 7.25.6 react: 19.0.0 + scheduler: 0.25.0 - react-is@18.3.1: {} - - react-is@19.0.0: {} + react-is@17.0.2: {} - react-test-renderer@19.0.0(react@19.0.0): - dependencies: - react: 19.0.0 - react-is: 19.0.0 - scheduler: 0.25.0 + react-is@18.3.1: {} react@19.0.0: {} @@ -13590,7 +13765,7 @@ snapshots: dependencies: typescript: 5.5.4 - ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.5.4))(typescript@5.5.4): + ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.5.4))(typescript@5.5.4): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -13608,6 +13783,25 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.25.2) + + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.5.4))(typescript@5.5.4): + dependencies: + bs-logger: 0.2.6 + ejs: 3.1.10 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@22.5.4) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.3 + typescript: 5.5.4 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) esbuild: 0.23.1 tsconfig-paths@3.15.0: