From cc1784b469ce46e3205d5f821f125103960ee750 Mon Sep 17 00:00:00 2001 From: Jacek Pudysz Date: Sat, 9 Mar 2024 19:35:14 +0100 Subject: [PATCH 1/3] feat: add library mocks --- src/common.ts | 1 + src/core/Unistyles.ts | 11 +++- src/core/mocks/UnistylesMockedBridge.ts | 73 +++++++++++++++++++++++ src/core/mocks/UnistylesMockedRegistry.ts | 23 +++++++ src/core/mocks/UnistylesMockedRuntime.ts | 65 ++++++++++++++++++++ src/core/mocks/index.ts | 3 + 6 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 src/core/mocks/UnistylesMockedBridge.ts create mode 100644 src/core/mocks/UnistylesMockedRegistry.ts create mode 100644 src/core/mocks/UnistylesMockedRuntime.ts create mode 100644 src/core/mocks/index.ts diff --git a/src/common.ts b/src/common.ts index 06714935..c71955e5 100644 --- a/src/common.ts +++ b/src/common.ts @@ -10,6 +10,7 @@ export const isAndroid = Platform.OS === 'android' export const isMobile = isIOS || isAndroid export const isServer = typeof window === 'undefined' export const isDev = process.env.NODE_ENV !== 'production' +export const isTest = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined || typeof jest !== 'undefined' export const ScreenOrientation = { Landscape: 'landscape', diff --git a/src/core/Unistyles.ts b/src/core/Unistyles.ts index b1b3dc06..21065c06 100644 --- a/src/core/Unistyles.ts +++ b/src/core/Unistyles.ts @@ -2,7 +2,8 @@ import { UnistylesModule } from './UnistylesModule' import { UnistylesRuntime } from './UnistylesRuntime' import { UnistyleRegistry } from './UnistyleRegistry' import type { UnistylesBridge } from '../types' -import { UnistylesError, isWeb } from '../common' +import { UnistylesError, isTest, isWeb } from '../common' +import { UnistylesMockedBridge, UnistylesMockedRegistry, UnistylesMockedRuntime } from './mocks' class Unistyles { private _runtime: UnistylesRuntime @@ -10,6 +11,14 @@ class Unistyles { private _bridge: UnistylesBridge constructor() { + if (isTest) { + this._bridge = new UnistylesMockedBridge() as unknown as UnistylesBridge + this._registry = new UnistylesMockedRegistry(this._bridge) as unknown as UnistyleRegistry + this._runtime = new UnistylesMockedRuntime(this._bridge, this._registry) as unknown as UnistylesRuntime + + return + } + const isInstalled = UnistylesModule?.install() ?? false if (!isInstalled) { diff --git a/src/core/mocks/UnistylesMockedBridge.ts b/src/core/mocks/UnistylesMockedBridge.ts new file mode 100644 index 00000000..20d580e8 --- /dev/null +++ b/src/core/mocks/UnistylesMockedBridge.ts @@ -0,0 +1,73 @@ +// @ts-nocheck +import type { UnistylesThemes, UnistylesBreakpoints } from '../../global' + +export class UnistylesMockedBridge { + constructor() {} + + public get screenWidth() { + return 400 + } + + public get screenHeight() { + return 860 + } + + public get enabledPlugins() { + return [] + } + + public get hasAdaptiveThemes() { + return true + } + + public get themeName() { + return 'default' + } + + public get breakpoint() { + return 'sm' + } + + public get colorScheme() { + return 'dark' + } + + public get contentSizeCategory() { + return 'unspecified' + } + + public get sortedBreakpointPairs() { + return [['sm', 320], ['md', 480], ['lg', 640], ['xl', 800]] + } + + public get insets() { + return { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + + public get statusBar() { + return { + height: 20, + width: 400 + } + } + + public get navigationBar() { + return { + height: 0, + width: 0 + } + } + + public set themes(themes: Array) {} + public useBreakpoints(breakpoints: UnistylesBreakpoints) {} + public useTheme(name: keyof UnistylesThemes) {} + public updateTheme(name: keyof UnistylesThemes) {} + public useAdaptiveThemes(enable: boolean) {} + public addPlugin(pluginName: string, notify: boolean) {} + public removePlugin(pluginName: string) {} +} diff --git a/src/core/mocks/UnistylesMockedRegistry.ts b/src/core/mocks/UnistylesMockedRegistry.ts new file mode 100644 index 00000000..ba7209c9 --- /dev/null +++ b/src/core/mocks/UnistylesMockedRegistry.ts @@ -0,0 +1,23 @@ +// @ts-nocheck +import type { UnistylesBreakpoints, UnistylesThemes } from '../../global' +import type { UnistylesPlugin, UnistylesConfig, UnistylesBridge } from '../../types' + +export class UnistylesMockedRegistry { + public config: UnistylesConfig = {} + public breakpoints: UnistylesBreakpoints = {} as UnistylesBreakpoints + public sortedBreakpointPairs: Array<[keyof UnistylesBreakpoints, UnistylesBreakpoints[keyof UnistylesBreakpoints]]> = [] + public plugins: Array = [] + public themes: UnistylesThemes = {} as UnistylesThemes + public themeNames: Array = [] + + constructor(private unistylesBridge: UnistylesBridge) {} + + public addThemes = (themes: UnistylesThemes) => this + public addBreakpoints = (breakpoints: UnistylesBreakpoints) => this + public addConfig = (config: UnistylesConfig) => this + public getTheme = (forName: keyof UnistylesThemes) => ({} as UnistylesThemes[keyof UnistylesThemes]) + public addPlugin = (plugin: UnistylesPlugin, notify: boolean = true) => {} + public removePlugin = (plugin: UnistylesPlugin) => {} + public updateTheme = (name: keyof UnistylesThemes, theme: UnistylesThemes[keyof UnistylesThemes]) => {} + public hasTheme = (name: keyof UnistylesThemes) => true +} diff --git a/src/core/mocks/UnistylesMockedRuntime.ts b/src/core/mocks/UnistylesMockedRuntime.ts new file mode 100644 index 00000000..a2bd7a7d --- /dev/null +++ b/src/core/mocks/UnistylesMockedRuntime.ts @@ -0,0 +1,65 @@ +// @ts-nocheck +import { ScreenOrientation } from '../../common' +import type { UnistylesBridge, UnistylesPlugin, UnistylesRegistry } from '../../types' +import type { UnistylesThemes } from '../../global' + +export class UnistylesMockedRuntime { + constructor(private unistylesBridge: UnistylesBridge, private unistylesRegistry: UnistylesRegistry) {} + + public get colorScheme() { + return this.unistylesBridge.colorScheme + } + + public get hasAdaptiveThemes() { + return this.unistylesBridge.hasAdaptiveThemes + } + + public get themeName() { + return this.unistylesBridge.themeName + } + + public get contentSizeCategory() { + return this.unistylesBridge.contentSizeCategory + } + + public get breakpoint() { + return this.unistylesBridge.breakpoint + } + + public get breakpoints() { + return this.unistylesRegistry.breakpoints + } + + public get enabledPlugins() { + return this.unistylesBridge.enabledPlugins + } + + public get screen() { + return { + width: this.unistylesBridge.screenWidth, + height: this.unistylesBridge.screenHeight + } + } + + public get insets() { + return this.unistylesBridge.insets + } + + public get statusBar() { + return this.unistylesBridge.statusBar + } + + public get navigationBar() { + return this.unistylesBridge.navigationBar + } + + public get orientation() { + return ScreenOrientation.Portrait + } + + public setTheme = (name: keyof UnistylesThemes) => true + public updateTheme = (name: keyof UnistylesThemes, theme: UnistylesThemes[keyof UnistylesThemes]) => {} + public setAdaptiveThemes = (enabled: boolean) => {} + public addPlugin = (plugin: UnistylesPlugin) => {} + public removePlugin = (plugin: UnistylesPlugin) => {} +} diff --git a/src/core/mocks/index.ts b/src/core/mocks/index.ts new file mode 100644 index 00000000..5af4f25a --- /dev/null +++ b/src/core/mocks/index.ts @@ -0,0 +1,3 @@ +export { UnistylesMockedBridge } from './UnistylesMockedBridge' +export { UnistylesMockedRegistry } from './UnistylesMockedRegistry' +export { UnistylesMockedRuntime } from './UnistylesMockedRuntime' From f9840f385c1325a460f9fc0e37891660320cadc6 Mon Sep 17 00:00:00 2001 From: Jacek Pudysz Date: Sat, 9 Mar 2024 21:36:03 +0100 Subject: [PATCH 2/3] feat: make mocks dynamic --- src/core/mocks/UnistylesMockedBridge.ts | 73 ++++------------------- src/core/mocks/UnistylesMockedRegistry.ts | 32 ++++++++-- src/core/mocks/UnistylesMockedRuntime.ts | 53 ++++++++++++---- 3 files changed, 81 insertions(+), 77 deletions(-) diff --git a/src/core/mocks/UnistylesMockedBridge.ts b/src/core/mocks/UnistylesMockedBridge.ts index 20d580e8..d167a5ad 100644 --- a/src/core/mocks/UnistylesMockedBridge.ts +++ b/src/core/mocks/UnistylesMockedBridge.ts @@ -4,66 +4,19 @@ import type { UnistylesThemes, UnistylesBreakpoints } from '../../global' export class UnistylesMockedBridge { constructor() {} - public get screenWidth() { - return 400 - } - - public get screenHeight() { - return 860 - } - - public get enabledPlugins() { - return [] - } - - public get hasAdaptiveThemes() { - return true - } - - public get themeName() { - return 'default' - } - - public get breakpoint() { - return 'sm' - } - - public get colorScheme() { - return 'dark' - } - - public get contentSizeCategory() { - return 'unspecified' - } - - public get sortedBreakpointPairs() { - return [['sm', 320], ['md', 480], ['lg', 640], ['xl', 800]] - } - - public get insets() { - return { - top: 0, - right: 0, - bottom: 0, - left: 0 - } - } - - public get statusBar() { - return { - height: 20, - width: 400 - } - } - - public get navigationBar() { - return { - height: 0, - width: 0 - } - } - - public set themes(themes: Array) {} + public screenWidth() {} + public screenHeight() {} + public enabledPlugins() {} + public hasAdaptiveThemes() {} + public themeName() {} + public breakpoint() {} + public colorScheme() {} + public contentSizeCategory() {} + public sortedBreakpointPairs() {} + public insets() {} + public statusBar() {} + public navigationBar() {} + public themes(themes: Array) {} public useBreakpoints(breakpoints: UnistylesBreakpoints) {} public useTheme(name: keyof UnistylesThemes) {} public updateTheme(name: keyof UnistylesThemes) {} diff --git a/src/core/mocks/UnistylesMockedRegistry.ts b/src/core/mocks/UnistylesMockedRegistry.ts index ba7209c9..2552e60d 100644 --- a/src/core/mocks/UnistylesMockedRegistry.ts +++ b/src/core/mocks/UnistylesMockedRegistry.ts @@ -12,10 +12,34 @@ export class UnistylesMockedRegistry { constructor(private unistylesBridge: UnistylesBridge) {} - public addThemes = (themes: UnistylesThemes) => this - public addBreakpoints = (breakpoints: UnistylesBreakpoints) => this - public addConfig = (config: UnistylesConfig) => this - public getTheme = (forName: keyof UnistylesThemes) => ({} as UnistylesThemes[keyof UnistylesThemes]) + public addThemes = (themes: UnistylesThemes) => { + this.themes = themes + this.themeNames = Object.keys(themes) as Array + + return this + } + public addBreakpoints = (breakpoints: UnistylesBreakpoints) => { + this.breakpoints = breakpoints + this.sortedBreakpointPairs = Object + .entries(breakpoints) + .sort((breakpoint1, breakpoint2) => { + const [, value1] = breakpoint1 + const [, value2] = breakpoint2 + + return value1 - value2 + }) as Array<[keyof UnistylesBreakpoints, UnistylesBreakpoints[keyof UnistylesBreakpoints]]> + + return this + } + public addConfig = (config: UnistylesConfig) => {} + public getTheme = (forName: keyof UnistylesThemes) => { + if (this.themeNames.length === 0) { + return {} as UnistylesThemes[keyof UnistylesThemes] + } + + return this.themes[forName] + } + public addPlugin = (plugin: UnistylesPlugin, notify: boolean = true) => {} public removePlugin = (plugin: UnistylesPlugin) => {} public updateTheme = (name: keyof UnistylesThemes, theme: UnistylesThemes[keyof UnistylesThemes]) => {} diff --git a/src/core/mocks/UnistylesMockedRuntime.ts b/src/core/mocks/UnistylesMockedRuntime.ts index a2bd7a7d..b76e20ba 100644 --- a/src/core/mocks/UnistylesMockedRuntime.ts +++ b/src/core/mocks/UnistylesMockedRuntime.ts @@ -1,29 +1,45 @@ // @ts-nocheck import { ScreenOrientation } from '../../common' -import type { UnistylesBridge, UnistylesPlugin, UnistylesRegistry } from '../../types' +import type { UnistylesBridge, UnistylesPlugin } from '../../types' import type { UnistylesThemes } from '../../global' +import type { UnistylesMockedRegistry } from './UnistylesMockedRegistry' +import type { UnistyleRegistry } from '../UnistyleRegistry' export class UnistylesMockedRuntime { - constructor(private unistylesBridge: UnistylesBridge, private unistylesRegistry: UnistylesRegistry) {} + private unistylesRegistry: UnistylesMockedRegistry + + constructor(private unistylesBridge: UnistylesBridge, private unistylesRegistry: UnistyleRegistry) { + this.unistylesRegistry = unistylesRegistry as unknown as UnistylesMockedRegistry + } public get colorScheme() { - return this.unistylesBridge.colorScheme + return 'dark' } public get hasAdaptiveThemes() { - return this.unistylesBridge.hasAdaptiveThemes + return true } public get themeName() { - return this.unistylesBridge.themeName + return this.unistylesRegistry.themeNames.length > 0 + ? this.unistylesRegistry.themeNames.at(0) + : undefined } public get contentSizeCategory() { - return this.unistylesBridge.contentSizeCategory + return 'unspecified' } public get breakpoint() { - return this.unistylesBridge.breakpoint + if (this.unistylesRegistry.sortedBreakpointPairs.length === 0) { + return undefined + } + + const firstBreakpoint = this.unistylesRegistry.sortedBreakpointPairs.at(0) + + return firstBreakpoint + ? firstBreakpoint.at(0) + : undefined } public get breakpoints() { @@ -31,26 +47,37 @@ export class UnistylesMockedRuntime { } public get enabledPlugins() { - return this.unistylesBridge.enabledPlugins + return this.unistylesRegistry.plugins } public get screen() { return { - width: this.unistylesBridge.screenWidth, - height: this.unistylesBridge.screenHeight + width: 360, + height: 800 } } public get insets() { - return this.unistylesBridge.insets + return { + top: 0, + right: 0, + bottom: 0, + left: 0 + } } public get statusBar() { - return this.unistylesBridge.statusBar + return { + height: 24, + width: 800 + } } public get navigationBar() { - return this.unistylesBridge.navigationBar + return { + height: 0, + width: 0 + } } public get orientation() { From 73b565d6471708fe26294104ebe83cd830af8a32 Mon Sep 17 00:00:00 2001 From: Jacek Pudysz Date: Sat, 9 Mar 2024 22:05:29 +0100 Subject: [PATCH 3/3] docs: add testing section --- docs/.astro/types.d.ts | 7 +++ docs/astro.config.mjs | 5 ++ docs/src/content/docs/reference/testing.mdx | 66 +++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 docs/src/content/docs/reference/testing.mdx diff --git a/docs/.astro/types.d.ts b/docs/.astro/types.d.ts index 1190ddfc..ccc2a977 100644 --- a/docs/.astro/types.d.ts +++ b/docs/.astro/types.d.ts @@ -297,6 +297,13 @@ declare module 'astro:content' { collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; +"reference/testing.mdx": { + id: "reference/testing.mdx"; + slug: "reference/testing"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; "reference/theming.mdx": { id: "reference/theming.mdx"; slug: "reference/theming"; diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 2173934f..eed51f64 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -122,6 +122,11 @@ export default defineConfig({ label: 'Debugging', link: '/reference/debugging/' }, + { + label: 'Testing', + link: '/reference/testing/', + badge: 'New' + }, { label: 'Errors', link: '/reference/errors/' diff --git a/docs/src/content/docs/reference/testing.mdx b/docs/src/content/docs/reference/testing.mdx new file mode 100644 index 00000000..d8868073 --- /dev/null +++ b/docs/src/content/docs/reference/testing.mdx @@ -0,0 +1,66 @@ +--- +title: Testing +--- + +import Seo from '../../../components/Seo.astro' +import Badge from '../../../components/Badge.astro' + + + + + + +Unistyles ships with own mocks that will help you to test your components that consume `useStyles` and `createStyleSheet`. + +### Using mocks + +In order to use the library mocks you need to either use `jest` or set `process.env.NODE_ENV` to `test`. + +```json /NODE_ENV=test/ +"scripts": { + "start": "react-native start", + "test": "NODE_ENV=test vitest" +}, +``` + +### NativeEventEmitter + +To mock `NativeEventEmitter` that is imported internally from `react-native` you can use the following code: + +1. Create `jest.setup.js` file: + +```javascript /NativeEventEmitter/ +jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter'); +``` + +2. If you use `jest.config.js`: + +```diff lang="js" +module.exports = { + preset: 'react-native', ++ setupFiles: ['./jest.setup.js'], +}; +``` + +3. If you use `jest` in `package.json`: + +```diff lang="json" +{ + "jest": { + "preset": "react-native", ++ "setupFiles": ["./jest.setup.js"] + } +} +``` + +### Support + +Mocks support basic operations and are designed to mimic the setting of themes and breakpoints. +However, some operations, such as using `plugins` or `adaptiveThemes`, are not supported. + +