Skip to content

Commit

Permalink
refactor: changes react tests to use testing-library
Browse files Browse the repository at this point in the history
  • Loading branch information
stalniy committed Jan 5, 2025
1 parent c8529e1 commit 4482931
Show file tree
Hide file tree
Showing 14 changed files with 414 additions and 284 deletions.
9 changes: 5 additions & 4 deletions packages/casl-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -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",
Expand Down
121 changes: 0 additions & 121 deletions packages/casl-react/spec/Can.spec.js

This file was deleted.

111 changes: 111 additions & 0 deletions packages/casl-react/spec/Can.spec.tsx
Original file line number Diff line number Diff line change
@@ -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(<Can I="read" a="Post" ability={ability}>{children}</Can>)

expect(children).toHaveBeenCalledWith(ability.can('read', 'Post'), ability)
})

it('unsubscribes from ability updates when unmounted', () => {
jest.spyOn(ability, 'can')
const component = render(<Can I='read' a='Post' ability={ability}>test</Can>)

component.unmount()
act(() => ability.update([]))

expect(ability.can).toHaveBeenCalledTimes(1)
})

describe('rendering', () => {
it('renders children if ability allows to perform an action', () => {
render(<Can I='read' a='Post' ability={ability}>I can see it</Can>)

expect(screen.queryByText('I can see it')).toBeTruthy()
})

it('does not render children if ability does not allow to perform an action', () => {
render(<Can I="update" a="Post" ability={ability}>I can see it</Can>)

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(<Can not={true} I="read" a="Post" ability={ability}>I can see it</Can>)

expect(screen.queryByText('I can see it')).not.toBeTruthy()
})

it('rerenders when ability rules are changed', () => {
render(<Can I="read" a="Post" ability={ability}>I can see it</Can>)
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(<Can I="update" a="Post" ability={ability}>I can see it</Can>)
expect(screen.queryByText('I can see it')).not.toBeTruthy()

component.rerender(<Can I="read" a="Post" ability={ability}>I can see it</Can>)
expect(screen.queryByText('I can see it')).toBeTruthy()
})

it('rerenders when `a` or `an` or `this` prop is changed', () => {
const component = render(<Can I="read" a="User" ability={ability}>I can see it</Can>)
expect(screen.queryByText('I can see it')).not.toBeTruthy()

component.rerender(<Can I="read" a="Post" ability={ability}>I can see it</Can>)
expect(screen.queryByText('I can see it')).toBeTruthy()
})

it('rerenders when `not` prop is changed', () => {
const component = render(<Can not={true} I="read" a="Post" ability={ability}>I can see it</Can>)
expect(screen.queryByText('I can see it')).not.toBeTruthy()

component.rerender(<Can not={false} I="read" a="Post" ability={ability}>I can see it</Can>)
expect(screen.queryByText('I can see it')).toBeTruthy()
})

it('does not rerender itself when previous ability rules are changed', () => {
const component = render(<Can I="read" a="Post" ability={ability}>I can see it</Can>)
const anotherAbility = defineAbility(can => can('manage', 'Post'))

jest.spyOn(ability, 'can')
component.rerender(<Can I="read" a="Post" ability={anotherAbility}>I can see it</Can>)
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(<Can I="read" a="Post" ability={ability}><>
<p>line 1</p>
<p>line 2</p>
</></Can>)

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(<Can I="delete" a="Post" passThrough={true} ability={ability}>{children}</Can>)

expect(ability.can('delete', 'Post')).toBe(false)
expect(children).toHaveBeenCalledWith(false, ability)
})
})
})
71 changes: 0 additions & 71 deletions packages/casl-react/spec/factory.spec.js

This file was deleted.

35 changes: 35 additions & 0 deletions packages/casl-react/spec/factory.spec.tsx
Original file line number Diff line number Diff line change
@@ -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<MongoAbility>
let ContextualCan: React.FunctionComponent<BoundCanProps<MongoAbility>>

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(<ContextualCan I="read" a="Post" ability={ability}>I see it</ContextualCan>)

expect(screen.queryByText('I see it')).toBeTruthy()
})

it('expects `Ability` instance to be provided by context Provider', () => {
render(<AbilityContext.Provider value={ability}>
<ContextualCan I="read" a="Post">I see it</ContextualCan>
</AbilityContext.Provider>)

expect(screen.queryByText('I see it')).toBeTruthy()
})

it('should not render anything if ability does not have rules', () => {
render(<ContextualCan I="read" a="Post">I see it</ContextualCan>)
expect(screen.queryByText('I see it')).not.toBeTruthy()
})
})
Loading

0 comments on commit 4482931

Please sign in to comment.