-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
react-experiment: change useLogger typing (#192)
also adds test for useLogger
- Loading branch information
1 parent
669e424
commit 7696b2f
Showing
7 changed files
with
105 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@lightmill/react-experiment': minor | ||
--- | ||
|
||
Add log content in use logger typing with default values. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import { Run, useLogger } from '../src/main.js'; | ||
import userEventPackage from '@testing-library/user-event'; | ||
|
||
// @ts-expect-error - userEventPackage is not typed correctly | ||
const userEvent: typeof userEventPackage.default = userEventPackage; | ||
|
||
describe('useLogger', () => { | ||
it('can be used with a provided type', async () => { | ||
const user = userEvent.setup(); | ||
const Task = () => { | ||
let handleTaskLog = useLogger<'task'>('task'); | ||
return ( | ||
<button | ||
onClick={() => { | ||
handleTaskLog({ value: 'value' }); | ||
}} | ||
> | ||
log | ||
</button> | ||
); | ||
}; | ||
let config = { | ||
tasks: { t: <Task /> }, | ||
completed: <div data-testid="end" />, | ||
}; | ||
const onLog = vi.fn(() => Promise.resolve()); | ||
render(<Run elements={config} timeline={[{ type: 't' }]} onLog={onLog} />); | ||
await user.click(screen.getByRole('button')); | ||
expect(onLog).toHaveBeenCalledWith({ | ||
type: 'task', | ||
value: 'value', | ||
}); | ||
}); | ||
|
||
it('can be used without providing a type', async () => { | ||
const user = userEvent.setup(); | ||
const Task = () => { | ||
let handleLog = useLogger(); | ||
return ( | ||
<button | ||
onClick={() => { | ||
handleLog({ type: 'task', value: 'value' }); | ||
}} | ||
> | ||
log | ||
</button> | ||
); | ||
}; | ||
let config = { | ||
tasks: { t: <Task /> }, | ||
completed: <div data-testid="end" />, | ||
}; | ||
const onLog = vi.fn(() => Promise.resolve()); | ||
render(<Run elements={config} timeline={[{ type: 't' }]} onLog={onLog} />); | ||
await user.click(screen.getByRole('button')); | ||
expect(onLog).toHaveBeenCalledWith({ | ||
type: 'task', | ||
value: 'value', | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,39 @@ | ||
import * as React from 'react'; | ||
import { RegisteredLog } from './config.js'; | ||
import { loggerContext } from './contexts.js'; | ||
import { loggerContext, noLoggerSymbol } from './contexts.js'; | ||
import { NotUnion } from './utils.js'; | ||
|
||
type Simplify<T> = { [K in keyof T]: T[K] } & {}; | ||
|
||
export function useLogger<T extends RegisteredLog['type']>( | ||
// This forces the user to provide one unique type of log, so that the type | ||
// of the log is inferred, and the injection of logType is correct. If the | ||
// user wants to log multiple types of logs, they should use useLogger without | ||
// argument. | ||
logType: NotUnion<T>, | ||
): (log: Simplify<Omit<RegisteredLog & { type: T }, 'type'>>) => void; | ||
type UntypedLogFromType<T extends RegisteredLog['type']> = Simplify< | ||
Omit<RegisteredLog & { type: T }, 'type'> | ||
>; | ||
|
||
export function useLogger< | ||
T extends RegisteredLog['type'], | ||
L extends UntypedLogFromType<NotUnion<T>> = UntypedLogFromType<NotUnion<T>>, | ||
>(logType: T): (log: L) => void; | ||
export function useLogger(): (log: RegisteredLog) => void; | ||
export function useLogger<T extends RegisteredLog['type']>( | ||
logType?: NotUnion<T>, | ||
): | ||
| ((log: RegisteredLog) => void) | ||
| ((log: Simplify<Omit<RegisteredLog & { type: T }, 'type'>>) => void) { | ||
export function useLogger< | ||
T extends RegisteredLog['type'], | ||
L extends UntypedLogFromType<NotUnion<T>> = UntypedLogFromType<NotUnion<T>>, | ||
>(logType?: T): ((log: RegisteredLog) => void) | ((log: L) => void) { | ||
const addLog = React.useContext(loggerContext); | ||
if (addLog == null) { | ||
throw new Error( | ||
'No logger found. Is this component rendered in a <Run />, and was a logger provided as a prop to <Run />?', | ||
'No logger found. Is this component rendered in a <Run />?', | ||
); | ||
} | ||
if (logType == null) { | ||
return addLog; | ||
if (addLog === noLoggerSymbol) { | ||
throw new Error('No logger found. Was onLog provided in <Run />?'); | ||
} | ||
return (log: Omit<RegisteredLog & { type: T }, 'type'>) => | ||
// LogType cannot be a union type so we it should be safe to cast log to | ||
// RegisteredLog. | ||
addLog({ ...log, type: logType } as unknown as RegisteredLog); | ||
|
||
return React.useMemo(() => { | ||
if (logType == null) { | ||
return addLog; | ||
} | ||
return (log: L) => { | ||
return addLog({ ...log, type: logType } as unknown as L & { type: T }); | ||
}; | ||
}, [addLog, logType]); | ||
} |