mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-21 13:39:22 +01:00
Logic tests again (#5234)
Co-authored-by: Paolo D'Amico <paolodamico@users.noreply.github.com>
This commit is contained in:
parent
7b316c6a6a
commit
535256cf69
@ -4,6 +4,7 @@ import { routerPlugin } from 'kea-router'
|
||||
import { loadersPlugin } from 'kea-loaders'
|
||||
import { windowValuesPlugin } from 'kea-window-values'
|
||||
import { errorToast, identifierToHuman } from 'lib/utils'
|
||||
import { waitForPlugin } from 'kea-waitfor'
|
||||
|
||||
/*
|
||||
Actions for which we don't want to show error alerts,
|
||||
@ -47,6 +48,7 @@ export function initKea(): void {
|
||||
;(window as any).Sentry?.captureException(error)
|
||||
},
|
||||
}),
|
||||
waitForPlugin,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
26
frontend/src/lib/api.mock.ts
Normal file
26
frontend/src/lib/api.mock.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import apiNoMock from 'lib/api'
|
||||
|
||||
type APIMockReturnType = {
|
||||
[K in keyof typeof apiNoMock]: jest.Mock<ReturnType<typeof apiNoMock[K]>, Parameters<typeof apiNoMock[K]>>
|
||||
}
|
||||
|
||||
type APIRoute = {
|
||||
pathname: string
|
||||
search: string
|
||||
searchParams: Record<string, any>
|
||||
hash: string
|
||||
hashParams: Record<string, any>
|
||||
url: string
|
||||
data?: Record<string, any>
|
||||
}
|
||||
|
||||
export const api = (apiNoMock as any) as APIMockReturnType
|
||||
|
||||
export const mockAPIGet = (cb: (url: APIRoute) => any): void => {
|
||||
beforeEach(async () => {
|
||||
api.get.mockImplementation(async (url, data?: Record<string, any>) => {
|
||||
// kea-router is mocked out, must `await import()` to get access to the utility
|
||||
return cb({ ...(await import('kea-router')).combineUrl(url), data })
|
||||
})
|
||||
})
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,105 @@
|
||||
import { infiniteListLogic } from './infiniteListLogic'
|
||||
import { BuiltLogic } from 'kea'
|
||||
import { waitForAction } from 'kea-waitfor'
|
||||
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
|
||||
import { infiniteListLogicType } from 'lib/components/TaxonomicFilter/infiniteListLogicType'
|
||||
import { mockAPIGet } from 'lib/api.mock'
|
||||
import { initKeaTestLogic } from '~/test/utils'
|
||||
import { mockEventDefinitions } from '~/test/mocks'
|
||||
|
||||
jest.mock('lib/api')
|
||||
|
||||
describe('infiniteListLogic verbose version', () => {
|
||||
let logic: BuiltLogic<infiniteListLogicType>
|
||||
|
||||
mockAPIGet(async ({ pathname, searchParams }) => {
|
||||
if (pathname === 'api/projects/@current/event_definitions') {
|
||||
const results = searchParams.search
|
||||
? mockEventDefinitions.filter((e) => e.name.includes(searchParams.search))
|
||||
: mockEventDefinitions
|
||||
return {
|
||||
results,
|
||||
count: results.length,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
initKeaTestLogic({
|
||||
logic: infiniteListLogic,
|
||||
props: {
|
||||
taxonomicFilterLogicKey: 'testList',
|
||||
listGroupType: TaxonomicFilterGroupType.Events,
|
||||
},
|
||||
waitFor: 'loadRemoteItemsSuccess',
|
||||
onLogic: (l) => (logic = l),
|
||||
})
|
||||
|
||||
describe('values', () => {
|
||||
it('has proper defaults', () => {
|
||||
expect(logic.values).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('loaders', () => {
|
||||
describe('remoteItems', () => {
|
||||
it('loads initial items on mount', async () => {
|
||||
expect(logic.values.remoteItems.results.length).toEqual(56)
|
||||
})
|
||||
|
||||
it('setting search query filters events', async () => {
|
||||
logic.actions.setSearchQuery('event')
|
||||
expect(logic.values.searchQuery).toEqual('event')
|
||||
|
||||
await waitForAction(logic.actions.loadRemoteItemsSuccess)
|
||||
expect(logic.values.remoteItems.results.length).toEqual(3)
|
||||
expect(logic.values.remoteItems).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('reducers', () => {
|
||||
describe('index', () => {
|
||||
it('is set via setIndex', async () => {
|
||||
expect(logic.values.index).toEqual(0)
|
||||
logic.actions.setIndex(1)
|
||||
expect(logic.values.index).toEqual(1)
|
||||
})
|
||||
|
||||
it('can go up and down', async () => {
|
||||
expect(logic.values.remoteItems.results.length).toEqual(56)
|
||||
|
||||
logic.actions.moveUp()
|
||||
expect(logic.values.index).toEqual(55)
|
||||
|
||||
logic.actions.moveUp()
|
||||
expect(logic.values.index).toEqual(54)
|
||||
|
||||
logic.actions.moveDown()
|
||||
expect(logic.values.index).toEqual(55)
|
||||
|
||||
logic.actions.moveDown()
|
||||
expect(logic.values.index).toEqual(0)
|
||||
|
||||
logic.actions.moveDown()
|
||||
expect(logic.values.index).toEqual(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('actions', () => {
|
||||
describe('selectSelected', () => {
|
||||
it('actually selects the selected', async () => {
|
||||
expect(logic.values.selectedItem).toEqual(expect.objectContaining({ name: 'event1' }))
|
||||
|
||||
logic.actions.selectItem = jest.fn()
|
||||
logic.actions.selectSelected()
|
||||
|
||||
expect(logic.actions.selectItem).toHaveBeenCalledWith(
|
||||
'events',
|
||||
'event1',
|
||||
expect.objectContaining({ name: 'event1' })
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -6,28 +6,10 @@ import { EventDefinitionStorage } from '~/models/eventDefinitionsModel'
|
||||
import { infiniteListLogicType } from './infiniteListLogicType'
|
||||
import { CohortType, EventDefinition } from '~/types'
|
||||
import Fuse from 'fuse.js'
|
||||
import { InfiniteListLogicProps } from 'lib/components/TaxonomicFilter/types'
|
||||
import { InfiniteListLogicProps, ListFuse, ListStorage, LoaderOptions } from 'lib/components/TaxonomicFilter/types'
|
||||
import { taxonomicFilterLogic } from 'lib/components/TaxonomicFilter/taxonomicFilterLogic'
|
||||
import { groups } from 'lib/components/TaxonomicFilter/groups'
|
||||
|
||||
interface ListStorage {
|
||||
results: (EventDefinition | CohortType)[]
|
||||
searchQuery?: string // Query used for the results currently in state
|
||||
count: number
|
||||
queryChanged?: boolean
|
||||
first?: boolean
|
||||
}
|
||||
|
||||
interface LoaderOptions {
|
||||
offset: number
|
||||
limit: number
|
||||
}
|
||||
|
||||
type ListFuse = Fuse<{
|
||||
name: string
|
||||
item: EventDefinition | CohortType
|
||||
}> // local alias for typegen
|
||||
|
||||
function appendAtIndex<T>(array: T[], items: any[], startIndex?: number): T[] {
|
||||
if (startIndex === undefined) {
|
||||
return [...array, ...items]
|
||||
@ -51,7 +33,7 @@ const API_CACHE_TIMEOUT = 60000
|
||||
const apiCache: Record<string, EventDefinitionStorage> = {}
|
||||
const apiCacheTimers: Record<string, number> = {}
|
||||
|
||||
export const infiniteListLogic = kea<infiniteListLogicType<ListFuse, ListStorage, LoaderOptions>>({
|
||||
export const infiniteListLogic = kea<infiniteListLogicType>({
|
||||
props: {} as InfiniteListLogicProps,
|
||||
|
||||
key: (props) => `${props.taxonomicFilterLogicKey}-${props.listGroupType}`,
|
||||
@ -235,7 +217,7 @@ export const infiniteListLogic = kea<infiniteListLogicType<ListFuse, ListStorage
|
||||
selectedItem: [(s) => [s.index, s.items], (index, items) => (index >= 0 ? items.results[index] : undefined)],
|
||||
selectedItemValue: [
|
||||
(s) => [s.selectedItem, s.group],
|
||||
(selectedItem, group) => group?.getValue?.(selectedItem) || null,
|
||||
(selectedItem, group) => (selectedItem ? group?.getValue?.(selectedItem) || null : null),
|
||||
],
|
||||
selectedItemInView: [
|
||||
(s) => [s.index, s.startIndex, s.stopIndex],
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
TaxonomicFilterValue,
|
||||
} from 'lib/components/TaxonomicFilter/types'
|
||||
import { infiniteListLogic } from 'lib/components/TaxonomicFilter/infiniteListLogic'
|
||||
import { groups } from 'lib/components/TaxonomicFilter/groups'
|
||||
|
||||
export const taxonomicFilterLogic = kea<taxonomicFilterLogicType>({
|
||||
props: {} as TaxonomicFilterLogicProps,
|
||||
@ -63,7 +64,7 @@ export const taxonomicFilterLogic = kea<taxonomicFilterLogicType>({
|
||||
],
|
||||
groupTypes: [
|
||||
() => [(_, props) => props.groupTypes],
|
||||
(groupTypes): TaxonomicFilterGroupType[] => groupTypes || [],
|
||||
(groupTypes): TaxonomicFilterGroupType[] => groupTypes || groups.map((g) => g.type),
|
||||
],
|
||||
value: [() => [(_, props) => props.value], (value) => value],
|
||||
groupType: [() => [(_, props) => props.groupType], (groupType) => groupType],
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { LogicWrapper } from 'kea'
|
||||
import { CohortType, EventDefinition } from '~/types'
|
||||
import Fuse from 'fuse.js'
|
||||
|
||||
export interface TaxonomicFilterProps {
|
||||
groupType?: TaxonomicFilterGroupType
|
||||
@ -36,7 +38,24 @@ export enum TaxonomicFilterGroupType {
|
||||
PersonProperties = 'person_properties',
|
||||
}
|
||||
|
||||
export interface InfiniteListLogicProps {
|
||||
taxonomicFilterLogicKey: string
|
||||
export interface InfiniteListLogicProps extends TaxonomicFilterLogicProps {
|
||||
listGroupType: TaxonomicFilterGroupType
|
||||
}
|
||||
|
||||
export interface ListStorage {
|
||||
results: (EventDefinition | CohortType)[]
|
||||
searchQuery?: string // Query used for the results currently in state
|
||||
count: number
|
||||
queryChanged?: boolean
|
||||
first?: boolean
|
||||
}
|
||||
|
||||
export interface LoaderOptions {
|
||||
offset: number
|
||||
limit: number
|
||||
}
|
||||
|
||||
export type ListFuse = Fuse<{
|
||||
name: string
|
||||
item: EventDefinition | CohortType
|
||||
}> // local alias for typegen
|
||||
|
17
frontend/src/test/mocks.ts
Normal file
17
frontend/src/test/mocks.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { EventDefinition } from '~/types'
|
||||
|
||||
export const mockEventDefinitions: EventDefinition[] = [
|
||||
'event1',
|
||||
'test event',
|
||||
'$click',
|
||||
'$autocapture',
|
||||
'search',
|
||||
'other event',
|
||||
...Array(50),
|
||||
].map((name, index) => ({
|
||||
id: `uuid-${index}-foobar`,
|
||||
name: name || `misc-${index}-generated`,
|
||||
description: `${name || 'name generation'} is the best!`,
|
||||
query_usage_30_day: index * 3 + 1,
|
||||
volume_30_day: index * 13 + 2,
|
||||
}))
|
33
frontend/src/test/utils.ts
Normal file
33
frontend/src/test/utils.ts
Normal file
@ -0,0 +1,33 @@
|
||||
// Utilities for frontend logic tests
|
||||
import { BuiltLogic, Logic, LogicWrapper } from 'kea'
|
||||
import { initKea } from '~/initKea'
|
||||
import { waitForAction } from 'kea-waitfor'
|
||||
|
||||
export function initKeaTestLogic<L extends Logic = Logic>({
|
||||
logic,
|
||||
props,
|
||||
waitFor,
|
||||
onLogic,
|
||||
}: {
|
||||
logic: LogicWrapper<L>
|
||||
props?: LogicWrapper<L>['props']
|
||||
waitFor?: string
|
||||
onLogic?: (l: BuiltLogic<L>) => any
|
||||
}): void {
|
||||
let builtLogic: BuiltLogic<L>
|
||||
let unmount: () => void
|
||||
|
||||
beforeEach(async () => {
|
||||
initKea()
|
||||
builtLogic = logic.build(props)
|
||||
await onLogic?.(builtLogic)
|
||||
unmount = builtLogic.mount()
|
||||
if (waitFor) {
|
||||
await waitForAction(builtLogic.actionTypes[waitFor])
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
unmount()
|
||||
})
|
||||
}
|
@ -130,7 +130,7 @@ export default {
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
setupFiles: ['../../jest.setup.ts'],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
setupFilesAfterEnv: ['givens/setup'],
|
||||
|
1
jest.setup.ts
Normal file
1
jest.setup.ts
Normal file
@ -0,0 +1 @@
|
||||
import 'whatwg-fetch'
|
@ -70,6 +70,7 @@
|
||||
"kea-loaders": "^0.4.0",
|
||||
"kea-localstorage": "^1.1.1",
|
||||
"kea-router": "^1.0.2",
|
||||
"kea-waitfor": "^0.2.0",
|
||||
"kea-window-values": "^0.0.1",
|
||||
"md5": "^2.3.0",
|
||||
"posthog-js": "1.12.1",
|
||||
@ -148,7 +149,8 @@
|
||||
"typescript": "^4.3.2",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "^2.1.2"
|
||||
|
10
yarn.lock
10
yarn.lock
@ -7212,6 +7212,11 @@ kea-typegen@^1.1.5:
|
||||
prettier "^2.3.1"
|
||||
yargs "^16.2.0"
|
||||
|
||||
kea-waitfor@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/kea-waitfor/-/kea-waitfor-0.2.0.tgz#8de370a8160f9cfddf9897598deb1aaf3aa20f9e"
|
||||
integrity sha512-lR+q/sw+OtURQuXsiHDHJXLMUv+hJLjTNoZeAZucO+JC1ul2VbbhqFQSD8VXea3bpAaK5BL4t/QucoY00IpXMQ==
|
||||
|
||||
kea-window-values@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/kea-window-values/-/kea-window-values-0.0.1.tgz#918eee6647507e2d3d5d19466b9561261e7bc8d7"
|
||||
@ -11785,6 +11790,11 @@ whatwg-encoding@^1.0.5:
|
||||
dependencies:
|
||||
iconv-lite "0.4.24"
|
||||
|
||||
whatwg-fetch@^3.6.2:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c"
|
||||
integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==
|
||||
|
||||
whatwg-mimetype@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
|
||||
|
Loading…
Reference in New Issue
Block a user