From 470461e6e8af0c95a7529f928baf578c93e20e91 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Tue, 1 Apr 2025 15:46:45 +0100 Subject: [PATCH 1/6] Setup tests for different utility functions and components --- jest-setup.ts | 1 + jest.config.ts | 4 +- package-lock.json | 11 + package.json | 1 + .../__snapshots__/plugin-box.test.tsx.snap | 16 + .../lib/components/plugin-box.test.tsx | 74 +++++ .../data-core/lib/components/plugin-box.tsx | 2 +- .../lib/components/widget-renderer.test.tsx | 89 ++++++ .../lib/components/widget-renderer.tsx | 8 +- packages/data-core/lib/config/config.test.ts | 51 ++++ .../data-core/lib/context/plugin-config.tsx | 2 +- .../lib/plugin-utils/resolve.test.ts | 76 +++++ .../data-core/lib/plugin-utils/resolve.ts | 2 +- packages/data-core/lib/schema/schema.test.ts | 90 ++++++ packages/data-plugins/lib/utils.test.ts | 243 ++++++++++++++++ packages/data-plugins/lib/utils.ts | 4 +- .../array-fieldset.elements.test.tsx.snap | 275 ++++++++++++++++++ .../array-fieldset.elements.test.tsx | 144 +++++++++ .../data-widgets/lib/components/elements.tsx | 2 +- .../lib/components/object-property.test.tsx | 102 +++++++ .../lib/components/object-property.tsx | 21 +- .../utils/__snapshots__/utils.test.ts.snap | 33 +++ packages/data-widgets/lib/utils/utils.test.ts | 78 +++++ 23 files changed, 1301 insertions(+), 28 deletions(-) create mode 100644 jest-setup.ts create mode 100644 packages/data-core/lib/components/__snapshots__/plugin-box.test.tsx.snap create mode 100644 packages/data-core/lib/components/plugin-box.test.tsx create mode 100644 packages/data-core/lib/components/widget-renderer.test.tsx create mode 100644 packages/data-core/lib/config/config.test.ts create mode 100644 packages/data-core/lib/plugin-utils/resolve.test.ts create mode 100644 packages/data-core/lib/schema/schema.test.ts create mode 100644 packages/data-plugins/lib/utils.test.ts create mode 100644 packages/data-widgets/lib/components/__snapshots__/array-fieldset.elements.test.tsx.snap create mode 100644 packages/data-widgets/lib/components/array-fieldset.elements.test.tsx create mode 100644 packages/data-widgets/lib/components/object-property.test.tsx create mode 100644 packages/data-widgets/lib/utils/__snapshots__/utils.test.ts.snap create mode 100644 packages/data-widgets/lib/utils/utils.test.ts diff --git a/jest-setup.ts b/jest-setup.ts new file mode 100644 index 0000000..7b0828b --- /dev/null +++ b/jest-setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/jest.config.ts b/jest.config.ts index e4c0c7f..19835a2 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -90,7 +90,7 @@ const config: Config = { // ], // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module - // moduleNameMapper: {}, + moduleNameMapper: { 'lodash-es': 'lodash' }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader // modulePathIgnorePatterns: [], @@ -137,7 +137,7 @@ const config: Config = { // setupFiles: [], // A list of paths to modules that run some code to configure or set up the testing framework before each test - // setupFilesAfterEnv: [], + setupFilesAfterEnv: ['/jest-setup.ts'], // The number of seconds after which a test is considered as slow and reported as such in the results. // slowTestThreshold: 5, diff --git a/package-lock.json b/package-lock.json index 60ceb9b..0984543 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@types/jest": "^29.5.14", "@types/node": "^22.10.2", "@types/rollup-plugin-peer-deps-external": "^2.2.5", + "@types/testing-library__jest-dom": "^5.14.9", "babel-jest": "^29.7.0", "eslint": "^9.13.0", "eslint-config-prettier": "^9.1.0", @@ -6416,6 +6417,16 @@ "@types/geojson": "*" } }, + "node_modules/@types/testing-library__jest-dom": { + "version": "5.14.9", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", + "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jest": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", diff --git a/package.json b/package.json index c4f8e99..2b4f3ab 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@types/jest": "^29.5.14", "@types/node": "^22.10.2", "@types/rollup-plugin-peer-deps-external": "^2.2.5", + "@types/testing-library__jest-dom": "^5.14.9", "babel-jest": "^29.7.0", "eslint": "^9.13.0", "eslint-config-prettier": "^9.1.0", diff --git a/packages/data-core/lib/components/__snapshots__/plugin-box.test.tsx.snap b/packages/data-core/lib/components/__snapshots__/plugin-box.test.tsx.snap new file mode 100644 index 0000000..cf1ccab --- /dev/null +++ b/packages/data-core/lib/components/__snapshots__/plugin-box.test.tsx.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PluginBox renders ErrorBox when editSchema is null 1`] = ` +
+ Plugin + + TestPlugin + + has no edit schema. +
+`; diff --git a/packages/data-core/lib/components/plugin-box.test.tsx b/packages/data-core/lib/components/plugin-box.test.tsx new file mode 100644 index 0000000..2967b35 --- /dev/null +++ b/packages/data-core/lib/components/plugin-box.test.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { Formik } from 'formik'; +import { ChakraProvider } from '@chakra-ui/react'; + +import { PluginBox } from './plugin-box'; + +const mockPlugin = { + name: 'TestPlugin', + editSchema: jest.fn() +}; + +// Custom renderer to add context or providers if needed +const renderWithProviders = ( + ui: React.ReactNode, + { renderOptions = {} } = {} +) => { + // You can wrap the component with any context providers here + return render(ui, { + wrapper: ({ children }) => ( + + {}}> + {children} + + + ), + ...renderOptions + }); +}; + +describe('PluginBox', () => { + it.only('renders ErrorBox when editSchema is null', () => { + mockPlugin.editSchema.mockReturnValue(null); + + const { getByTestId } = renderWithProviders( + {}}> + + {({ field }) =>
{field.label}
} +
+
+ ); + + expect(getByTestId('plugin-box-error')).toMatchSnapshot(); + }); + + it('renders nothing when editSchema is Plugin.HIDDEN', () => { + mockPlugin.editSchema.mockReturnValue('HIDDEN'); + + const { container } = renderWithProviders( + {}}> + + {({ field }) =>
{field.label}
} +
+
+ ); + + expect(container.firstChild).toBeNull(); + }); + + it('renders children when editSchema is valid', () => { + const mockSchema = { type: 'string', label: 'testField' }; + mockPlugin.editSchema.mockReturnValue(mockSchema); + + const { getByText } = renderWithProviders( + {}}> + + {({ field }) =>
{field.label}
} +
+
+ ); + + expect(getByText(/testField/i)).toBeInTheDocument(); + }); +}); diff --git a/packages/data-core/lib/components/plugin-box.tsx b/packages/data-core/lib/components/plugin-box.tsx index 084e1fa..6b95a83 100644 --- a/packages/data-core/lib/components/plugin-box.tsx +++ b/packages/data-core/lib/components/plugin-box.tsx @@ -27,7 +27,7 @@ export function PluginBox(props: PluginBoxProps) { if (!editSchema) { return ( - + Plugin {plugin.name} has no edit schema. ); diff --git a/packages/data-core/lib/components/widget-renderer.test.tsx b/packages/data-core/lib/components/widget-renderer.test.tsx new file mode 100644 index 0000000..60d8aaf --- /dev/null +++ b/packages/data-core/lib/components/widget-renderer.test.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { ChakraProvider } from '@chakra-ui/react'; + +import { WidgetRenderer } from './widget-renderer'; +import { usePluginConfig } from '../context/plugin-config'; +import { SchemaField } from '../schema/types'; + +jest.mock('../context/plugin-config', () => ({ + usePluginConfig: jest.fn() +})); + +const mockPluginConfig = { + 'ui:widget': { + text: ({ pointer }: { pointer: string }) => ( +
Text Widget: {pointer}
+ ), + number: ({ pointer }: { pointer: string }) => ( +
Number Widget: {pointer}
+ ), + radio: ({ pointer }: { pointer: string }) => ( +
Radio Widget: {pointer}
+ ), + broken: () => { + throw new Error('Widget failed'); + } + } +}; + +describe('WidgetRenderer', () => { + beforeEach(() => { + (usePluginConfig as jest.Mock).mockReturnValue(mockPluginConfig); + }); + + it('renders a text widget', () => { + const field: SchemaField = { type: 'string' }; + render(); + expect(screen.getByText('Text Widget: test.pointer')).toBeInTheDocument(); + }); + + it('renders a number widget', () => { + const field: SchemaField = { type: 'number' }; + render(); + expect(screen.getByText('Number Widget: test.pointer')).toBeInTheDocument(); + }); + + it('renders a radio widget for enum strings', () => { + const field: SchemaField = { + type: 'string', + enum: [ + ['option1', 'Option 1'], + ['option2', 'Option 2'] + ] + }; + render(); + expect(screen.getByText('Radio Widget: test.pointer')).toBeInTheDocument(); + }); + + it('renders an error box when widget is not found', () => { + const field: SchemaField = { type: 'string', 'ui:widget': 'custom' }; + render( + + + + ); + expect(screen.getByText('Widget "custom" not found')).toBeInTheDocument(); + }); + + it('renders error boundary when widget throws an error', () => { + // The test will pass but there will be some noise in the output: + // Error: Uncaught [Error: Widget failed] + // So we need to spyOn the console error: + jest.spyOn(console, 'error').mockImplementation(() => null); + + const field: SchemaField = { type: 'string', 'ui:widget': 'broken' }; + render( + + + + ); + expect( + screen.getByText('💔 Error rendering widget (broken)') + ).toBeInTheDocument(); + expect(screen.getByText('Widget failed')).toBeInTheDocument(); + + // Restore the original console.error to avoid affecting other tests. + jest.spyOn(console, 'error').mockRestore(); + }); +}); diff --git a/packages/data-core/lib/components/widget-renderer.tsx b/packages/data-core/lib/components/widget-renderer.tsx index b140596..3178451 100644 --- a/packages/data-core/lib/components/widget-renderer.tsx +++ b/packages/data-core/lib/components/widget-renderer.tsx @@ -1,9 +1,9 @@ import React from 'react'; +import { Box, Text } from '@chakra-ui/react'; import { usePluginConfig } from '../context/plugin-config'; import { ErrorBox } from './error-box'; import { SchemaField } from '../schema/types'; -import { Box, Text } from '@chakra-ui/react'; interface WidgetProps { pointer: string; @@ -26,7 +26,7 @@ export function WidgetRenderer(props: WidgetProps) { const Widget = config['ui:widget'][widget]; return ( - + {Widget ? ( ) : ( @@ -75,7 +75,7 @@ export function WidgetRenderer(props: WidgetProps) { interface WidgetErrorBoundaryProps { children: React.ReactNode; field: SchemaField; - widget: string; + widgetName: string; pointer: string; } @@ -101,7 +101,7 @@ class WidgetErrorBoundary extends React.Component< return ( - 💔 Error rendering widget ({this.props.widget}) + 💔 Error rendering widget ({this.props.widgetName}) {this.state.error.message || 'Something is wrong with this widget'} diff --git a/packages/data-core/lib/config/config.test.ts b/packages/data-core/lib/config/config.test.ts new file mode 100644 index 0000000..195b01f --- /dev/null +++ b/packages/data-core/lib/config/config.test.ts @@ -0,0 +1,51 @@ +import { extendPluginConfig } from './index'; +import { PluginConfig } from './index'; + +describe('extendPluginConfig', () => { + it('should merge multiple configurations', () => { + const config1: Partial = { + collectionPlugins: [{ name: 'plugin1' } as any], + itemPlugins: [{ name: 'itemPlugin1' } as any], + 'ui:widget': { widget1: () => null } + }; + + const config2: Partial = { + collectionPlugins: [{ name: 'plugin2' } as any], + 'ui:widget': { widget2: () => null } + }; + + const result = extendPluginConfig(config1, config2); + + expect(result).toEqual({ + collectionPlugins: [{ name: 'plugin1' }, { name: 'plugin2' }], + itemPlugins: [{ name: 'itemPlugin1' }], + 'ui:widget': { + widget1: expect.any(Function), + widget2: expect.any(Function) + } + }); + }); + + it('should handle empty configurations', () => { + const result = extendPluginConfig(); + expect(result).toEqual({ + collectionPlugins: [], + itemPlugins: [], + 'ui:widget': {} + }); + }); + + it('should override properties with later configurations', () => { + const config1: Partial = { + 'ui:widget': { widget1: () => 'old' } + }; + + const config2: Partial = { + 'ui:widget': { widget1: () => 'new' } + }; + + const result = extendPluginConfig(config1, config2); + + expect(result['ui:widget'].widget1({} as any)).toBe('new'); + }); +}); diff --git a/packages/data-core/lib/context/plugin-config.tsx b/packages/data-core/lib/context/plugin-config.tsx index 7ac8958..2b34a4f 100644 --- a/packages/data-core/lib/context/plugin-config.tsx +++ b/packages/data-core/lib/context/plugin-config.tsx @@ -34,7 +34,7 @@ export const usePluginConfig = () => { if (!context) { throw new Error( - 'usePluginConfig must be used within a PluginConfigContextProvider' + 'usePluginConfig must be used within a PluginConfigProvider' ); } diff --git a/packages/data-core/lib/plugin-utils/resolve.test.ts b/packages/data-core/lib/plugin-utils/resolve.test.ts new file mode 100644 index 0000000..4df8523 --- /dev/null +++ b/packages/data-core/lib/plugin-utils/resolve.test.ts @@ -0,0 +1,76 @@ +import { resolvePlugins, applyHooks } from './resolve'; +import { Plugin } from './plugin'; + +class MockPlugin extends Plugin { + name = 'MockPlugin'; + init = jest.fn(); + editSchema = jest.fn(); +} + +describe('resolvePlugins', () => { + it('should resolve an array of Plugin instances', () => { + const plugin = new MockPlugin(); + const result = resolvePlugins([plugin], {}); + expect(result).toContainEqual(plugin); + }); + + it('should resolve functions returning Plugin instances', () => { + const plugin = new MockPlugin(); + const result = resolvePlugins([() => plugin], {}); + expect(result).toContainEqual(plugin); + }); + + it('should filter out invalid items', () => { + const plugin = new MockPlugin(); + const result = resolvePlugins([plugin, null, undefined], {}); + expect(result).toContainEqual(plugin); + expect(result.length).toBe(1); + }); +}); + +describe('applyHooks', () => { + it('should apply onAfterInit hooks', async () => { + const targetPlugin = new MockPlugin(); + const sourcePlugin = new MockPlugin(); + sourcePlugin[Plugin.HOOKS] = [ + { + name: targetPlugin.name, + onAfterInit: jest.fn() + } + ]; + + // applyHooks creates a copy, so we need the plugins back. + const [newTargetPl, newSourcePl] = applyHooks([targetPlugin, sourcePlugin]); + await newTargetPl.init({}); + expect(newSourcePl[Plugin.HOOKS][0].onAfterInit).toHaveBeenCalled(); + }); + + it('should compose onAfterEditSchema hooks', () => { + const targetPlugin = new MockPlugin(); + const sourcePlugin = new MockPlugin(); + sourcePlugin[Plugin.HOOKS] = [ + { + name: targetPlugin.name, + onAfterEditSchema: jest.fn((_, __, origEditSchema) => origEditSchema) + } + ]; + + // applyHooks creates a copy, so we need the plugins back. + const [newTargetPl, newSourcePl] = applyHooks([targetPlugin, sourcePlugin]); + newTargetPl.editSchema({}); + expect(newSourcePl[Plugin.HOOKS][0].onAfterEditSchema).toHaveBeenCalled(); + }); + + it('should ignore hooks targeting non-existent plugins', () => { + const sourcePlugin = new MockPlugin(); + sourcePlugin[Plugin.HOOKS] = [ + { + name: 'NonExistentPlugin', + onAfterInit: jest.fn() + } + ]; + + const plugins = applyHooks([sourcePlugin]); + expect(plugins).toContainEqual(sourcePlugin); + }); +}); diff --git a/packages/data-core/lib/plugin-utils/resolve.ts b/packages/data-core/lib/plugin-utils/resolve.ts index 075b9ca..2785ba7 100644 --- a/packages/data-core/lib/plugin-utils/resolve.ts +++ b/packages/data-core/lib/plugin-utils/resolve.ts @@ -69,7 +69,7 @@ export const applyHooks = (plugins: Plugin[]) => { for (const plSource of pluginsCopy) { for (const hook of plSource[Plugin.HOOKS]) { // Target where to apply the hook - const plTarget = plugins.find((p) => p.name === hook.name); + const plTarget = pluginsCopy.find((p) => p.name === hook.name); if (!plTarget) { continue; } diff --git a/packages/data-core/lib/schema/schema.test.ts b/packages/data-core/lib/schema/schema.test.ts new file mode 100644 index 0000000..1116817 --- /dev/null +++ b/packages/data-core/lib/schema/schema.test.ts @@ -0,0 +1,90 @@ +import { schemaToFormDataStructure } from './index'; +import { SchemaField } from './types'; + +describe('schemaToFormDataStructure', () => { + it('should handle root/object type fields', () => { + const schema: SchemaField = { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'string' } + } + }; + const result = schemaToFormDataStructure(schema); + expect(result).toEqual({ name: '', age: '' }); + }); + + it('should handle array type fields with minItems', () => { + const schema: SchemaField = { + type: 'array', + minItems: 2, + items: { type: 'string' } + }; + const result = schemaToFormDataStructure(schema); + expect(result).toEqual(['', '']); + }); + + it('should handle array type fields without minItems', () => { + const schema: SchemaField = { + type: 'array', + items: { type: 'string' } + }; + const result = schemaToFormDataStructure(schema); + expect(result).toEqual([]); + }); + + it('should handle json type fields', () => { + const schema: SchemaField = { + type: 'json' + }; + const result = schemaToFormDataStructure(schema); + expect(result).toEqual({}); + }); + + it('should handle default case for unsupported types', () => { + const schema: SchemaField = { + type: 'string' + }; + const result = schemaToFormDataStructure(schema); + expect(result).toEqual(''); + }); + + it('should handle deeply nested schemas', () => { + const schema: SchemaField = { + type: 'root', + properties: { + users: { + type: 'array', + minItems: 1, + items: { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'string' }, + accounts: { + type: 'array', + items: { + type: 'object', + properties: { + accountName: { type: 'string' }, + balance: { type: 'string' } + } + } + } + } + } + } + } + }; + const result = schemaToFormDataStructure(schema); + expect(result).toEqual({ + users: [ + { + name: '', + age: '', + accounts: [] + } + ] + }); + }); +}); diff --git a/packages/data-plugins/lib/utils.test.ts b/packages/data-plugins/lib/utils.test.ts new file mode 100644 index 0000000..cecb856 --- /dev/null +++ b/packages/data-plugins/lib/utils.test.ts @@ -0,0 +1,243 @@ +import { Plugin } from '@stac-manager/data-core'; +import { + fieldIf, + emptyString2Null, + null2EmptyString, + object2Array, + object2Tuple, + array2Object, + tuple2Object, + hasStacExtension, + addStacExtensionOption +} from './utils'; + +describe('Utils', () => { + describe('fieldIf', () => { + it('should return the ifTrue schema when condition is true', () => { + const result = fieldIf(true, 'test', { type: 'string' }); + expect(result).toEqual({ test: { type: 'string' } }); + }); + + it('should return the ifFalse schema when condition is false', () => { + const result = fieldIf( + false, + 'test', + { type: 'string' }, + { type: 'number' } + ); + expect(result).toEqual({ test: { type: 'number' } }); + }); + + it('should return an empty object when condition is false and ifFalse is not provided', () => { + const result = fieldIf(false, 'test', { type: 'string' }); + expect(result).toEqual({}); + }); + }); + + describe('emptyString2Null', () => { + it('should convert empty strings to null', () => { + expect(emptyString2Null(['', 'test', ['']])).toEqual([ + null, + 'test', + [null] + ]); + }); + + it('should leave non-empty values unchanged', () => { + expect(emptyString2Null(['test', 123, null])).toEqual([ + 'test', + 123, + null + ]); + }); + }); + + describe('null2EmptyString', () => { + it('should convert null values to empty strings', () => { + expect(null2EmptyString([null, 'test', [null]])).toEqual([ + '', + 'test', + [''] + ]); + }); + + it('should leave non-null values unchanged', () => { + expect(null2EmptyString(['test', 123, ''])).toEqual(['test', 123, '']); + }); + }); + + describe('object2Array', () => { + it('should convert an object to an array of objects', () => { + const input = { a: { name: 'Alice' }, b: { name: 'Bob' } }; + const result = object2Array(input, 'id'); + expect(result).toEqual([ + { id: 'a', name: 'Alice' }, + { id: 'b', name: 'Bob' } + ]); + }); + + it('should apply the transformation function if provided', () => { + const input = { a: { value: 1 }, b: { value: 2 } }; + const result = object2Array(input, 'id', (v) => ({ value: v.value * 2 })); + expect(result).toEqual([ + { id: 'a', value: 2 }, + { id: 'b', value: 4 } + ]); + }); + }); + + describe('object2Tuple', () => { + it('should convert an object to an array of tuples', () => { + const input = { a: 1, b: 2 }; + const result = object2Tuple(input); + expect(result).toEqual([ + ['a', 1], + ['b', 2] + ]); + }); + }); + + describe('array2Object', () => { + it('should convert an array of objects to an object', () => { + const input = [ + { id: 'a', value: 1 }, + { id: 'b', value: 2 } + ]; + const result = array2Object(input, 'id'); + expect(result).toEqual({ + a: { value: 1 }, + b: { value: 2 } + }); + }); + + it('should apply the transformation function if provided', () => { + const input = [ + { id: 'a', value: 1 }, + { id: 'b', value: 2 } + ]; + const result = array2Object(input, 'id', (v) => ({ value: v.value * 2 })); + expect(result).toEqual({ + a: { value: 2 }, + b: { value: 4 } + }); + }); + }); + + describe('tuple2Object', () => { + it('should convert an array of tuples to an object', () => { + const input = [ + ['a', 1], + ['b', 2] + ] as [string, any][]; + const result = tuple2Object(input); + expect(result).toEqual({ a: 1, b: 2 }); + }); + }); + + describe('hasStacExtension', () => { + it('should return true if the extension exists', () => { + const data = { + stac_extensions: [ + 'https://stac-extensions.github.io/item-assets/v1.0.0/schema.json' + ] + }; + const result = hasStacExtension(data, 'item-assets'); + expect(result).toBe(true); + }); + + it('should return false if the extension does not exist', () => { + const data = { + stac_extensions: [ + 'https://stac-extensions.github.io/eo/v1.0.0/schema.json' + ] + }; + const result = hasStacExtension(data, 'item-assets'); + expect(result).toBe(false); + }); + + it('should return true if the extension exists and version matches', () => { + const data = { + stac_extensions: [ + 'https://stac-extensions.github.io/item-assets/v1.0.0/schema.json' + ] + }; + const result = hasStacExtension( + data, + 'item-assets', + (v) => v === '1.0.0' + ); + expect(result).toBe(true); + }); + + it('should return false if the extension exists but version does not match', () => { + const data = { + stac_extensions: [ + 'https://stac-extensions.github.io/item-assets/v1.0.0/schema.json' + ] + }; + const result = hasStacExtension( + data, + 'item-assets', + (v) => v === '2.0.0' + ); + expect(result).toBe(false); + }); + }); + + describe('addStacExtensionOption', () => { + it('should add a new STAC extension option to the schema', () => { + const mockPlugin = { + registerHook: jest.fn() + } as unknown as Plugin; + + const label = 'New Extension'; + const value = 'https://example.com/new-extension/v1.0.0/schema.json'; + + addStacExtensionOption(mockPlugin, label, value); + + expect(mockPlugin.registerHook).toHaveBeenCalledWith( + expect.any(String), + 'onAfterEditSchema', + expect.any(Function) + ); + + // @ts-expect-error mock doesn't exist because ot os cast as Plugin + const hookCallback = mockPlugin.registerHook.mock.calls[0][2]; + const schema = { + properties: { + stac_extensions: { + items: { + enum: [] + } + } + } + }; + + const result = hookCallback(null, null, schema); + + expect(result.properties.stac_extensions.items.enum).toContainEqual([ + value, + label + ]); + }); + + it('should return the schema unchanged if it is invalid', () => { + const mockPlugin = { + registerHook: jest.fn() + } as unknown as Plugin; + + const label = 'New Extension'; + const value = 'https://example.com/new-extension/v1.0.0/schema.json'; + + addStacExtensionOption(mockPlugin, label, value); + + // @ts-expect-error mock doesn't exist because ot os cast as Plugin + const hookCallback = mockPlugin.registerHook.mock.calls[0][2]; + + const invalidSchema = null; + const result = hookCallback(null, null, invalidSchema); + + expect(result).toBeNull(); + }); + }); +}); diff --git a/packages/data-plugins/lib/utils.ts b/packages/data-plugins/lib/utils.ts index 8a59eca..3b0eb44 100644 --- a/packages/data-plugins/lib/utils.ts +++ b/packages/data-plugins/lib/utils.ts @@ -27,7 +27,7 @@ export function fieldIf( } : ifFalse ? { - [id]: ifTrue + [id]: ifFalse } : {} ) as Record; @@ -171,7 +171,7 @@ export function array2Object( * console.log(result); // { a: 1, b: 2, c: 3 } * ``` */ -export function tuple2Object(stack: string[][]) { +export function tuple2Object(stack: [string, any][]) { return (stack || []).reduce((acc, [key, item]) => { return { ...acc, [key]: item }; }, {}); diff --git a/packages/data-widgets/lib/components/__snapshots__/array-fieldset.elements.test.tsx.snap b/packages/data-widgets/lib/components/__snapshots__/array-fieldset.elements.test.tsx.snap new file mode 100644 index 0000000..4554f5f --- /dev/null +++ b/packages/data-widgets/lib/components/__snapshots__/array-fieldset.elements.test.tsx.snap @@ -0,0 +1,275 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ArrayFieldset renders layout correctly 1`] = ` +
+
+
+ Child Content +
+
+
+`; + +exports[`ArrayFieldset renders layout correctly 2`] = ` +
+
+
+ + Test Label + +
+
+
+
+ Child Content +
+
+
+`; + +exports[`ArrayFieldset renders layout correctly 3`] = ` +
+
+
+ + Test Label + +
+
+
+
+ Child Content +
+
+
+ +
+
+`; + +exports[`ArrayFieldset renders layout correctly 4`] = ` +
+
+
+ + Test Label + +
+
+ +
+
+
+
+ Child Content +
+
+
+ +
+
+`; + +exports[`ArrayFieldset renders layout correctly 5`] = ` +
+
+
+ + Test Label + + +
+
+ +
+
+
+
+ Child Content +
+
+
+ +
+
+`; diff --git a/packages/data-widgets/lib/components/array-fieldset.elements.test.tsx b/packages/data-widgets/lib/components/array-fieldset.elements.test.tsx new file mode 100644 index 0000000..869c19a --- /dev/null +++ b/packages/data-widgets/lib/components/array-fieldset.elements.test.tsx @@ -0,0 +1,144 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { ChakraProvider } from '@chakra-ui/react'; +import { ArrayFieldset } from './elements'; + +// Custom renderer to add context or providers if needed +const renderWithProviders = ( + ui: React.ReactNode, + { renderOptions = {} } = {} +) => { + // You can wrap the component with any context providers here + return render(ui, { + wrapper: ({ children }) => {children}, + ...renderOptions + }); +}; + +describe('ArrayFieldset', () => { + it('renders the label and children correctly', () => { + renderWithProviders( + +
Child Content
+
+ ); + + expect(screen.getByText('Test Label')).toBeInTheDocument(); + expect(screen.getByText('*')).toBeInTheDocument(); + expect(screen.getByText('Child Content')).toBeInTheDocument(); + }); + + it('calls onRemove when the remove button is clicked', () => { + const onRemove = jest.fn(); + renderWithProviders( + +
Child Content
+
+ ); + + const removeButton = screen.getByLabelText('Remove item'); + fireEvent.click(removeButton); + + expect(onRemove).toHaveBeenCalledTimes(1); + }); + + it('disables the remove button when removeDisabled is true', () => { + renderWithProviders( + {}} removeDisabled> +
Child Content
+
+ ); + + const removeButton = screen.getByLabelText('Remove item'); + expect(removeButton).toBeDisabled(); + }); + + it('calls onAdd when the add button is clicked', () => { + const onAdd = jest.fn(); + renderWithProviders( + +
Child Content
+
+ ); + + const addButton = screen.getByLabelText('Add item'); + fireEvent.click(addButton); + + expect(onAdd).toHaveBeenCalledTimes(1); + }); + + it('disables the add button when addDisabled is true', () => { + renderWithProviders( + {}} addDisabled> +
Child Content
+
+ ); + + const addButton = screen.getByLabelText('Add item'); + expect(addButton).toBeDisabled(); + }); + + it('does not render the remove button when onRemove is not provided', () => { + renderWithProviders( + +
Child Content
+
+ ); + + expect(screen.queryByLabelText('Remove item')).not.toBeInTheDocument(); + }); + + it('does not render the add button when onAdd is not provided', () => { + renderWithProviders( + +
Child Content
+
+ ); + + expect(screen.queryByLabelText('Add item')).not.toBeInTheDocument(); + }); + + it('renders layout correctly', () => { + const { rerender, container } = renderWithProviders( + +
Child Content
+
+ ); + const fieldset = container.querySelector('fieldset'); + expect(fieldset).toMatchSnapshot(); + + rerender( + +
Child Content
+
+ ); + expect(fieldset).toMatchSnapshot(); + + rerender( + {}}> +
Child Content
+
+ ); + expect(fieldset).toMatchSnapshot(); + + rerender( + {}} onRemove={() => {}}> +
Child Content
+
+ ); + expect(fieldset).toMatchSnapshot(); + + rerender( + {}} + onRemove={() => {}} + isRequired + > +
Child Content
+
+ ); + expect(fieldset).toMatchSnapshot(); + }); +}); diff --git a/packages/data-widgets/lib/components/elements.tsx b/packages/data-widgets/lib/components/elements.tsx index d69f63d..6f8a2f4 100644 --- a/packages/data-widgets/lib/components/elements.tsx +++ b/packages/data-widgets/lib/components/elements.tsx @@ -95,7 +95,7 @@ export const FieldsetDeleteBtn = forwardRef( ); interface ArrayFieldsetProps { - label: React.ReactNode; + label?: React.ReactNode; isRequired?: boolean; children: React.ReactNode; onRemove?: () => void; diff --git a/packages/data-widgets/lib/components/object-property.test.tsx b/packages/data-widgets/lib/components/object-property.test.tsx new file mode 100644 index 0000000..0f28940 --- /dev/null +++ b/packages/data-widgets/lib/components/object-property.test.tsx @@ -0,0 +1,102 @@ +import { + inferFieldType, + getFieldSchema, + replaceObjectKeyAt +} from './object-property'; + +describe('Utility Functions', () => { + describe('inferFieldType', () => { + it('infers "number" for numeric values', () => { + expect(inferFieldType(42)).toBe('number'); + }); + + it('infers "string[]" for arrays of strings', () => { + expect(inferFieldType(['a', 'b', 'c'])).toBe('string[]'); + }); + + it('infers "number[]" for arrays of numbers', () => { + expect(inferFieldType([1, 2, 3])).toBe('number[]'); + }); + + it('infers "json" for arrays with mixed types', () => { + expect(inferFieldType([1, 'a', true])).toBe('json'); + }); + + it('infers "json" for objects', () => { + expect(inferFieldType({ key: 'value' })).toBe('json'); + }); + + it('infers "string" for other types', () => { + expect(inferFieldType('hello')).toBe('string'); + }); + }); + + describe('getFieldSchema', () => { + it('returns schema for "string"', () => { + expect(getFieldSchema('string')).toEqual({ + type: 'string', + label: 'Value' + }); + }); + + it('returns schema for "number"', () => { + expect(getFieldSchema('number')).toEqual({ + type: 'number', + label: 'Value' + }); + }); + + it('returns schema for "string[]"', () => { + expect(getFieldSchema('string[]')).toEqual({ + type: 'array', + label: 'Value', + minItems: 1, + items: { type: 'string' } + }); + }); + + it('returns schema for "number[]"', () => { + expect(getFieldSchema('number[]')).toEqual({ + type: 'array', + label: 'Value', + minItems: 1, + items: { type: 'number' } + }); + }); + + it('returns schema for "json"', () => { + expect(getFieldSchema('json')).toEqual({ type: 'json', label: 'Value' }); + }); + + it('returns null for unknown types', () => { + expect(getFieldSchema('unknown' as any)).toBeNull(); + }); + }); + + describe('replaceObjectKeyAt', () => { + it('replaces a key at the root level', () => { + const obj = { oldKey: 'value' }; + const result = replaceObjectKeyAt(obj, 'oldKey', 'newKey'); + expect(result).toEqual({ newKey: 'value' }); + }); + + it('replaces a key at a nested path', () => { + const obj = { nested: { oldKey: 'value' } }; + const result = replaceObjectKeyAt(obj, 'nested.oldKey', 'newKey'); + expect(result).toEqual({ nested: { newKey: 'value' } }); + }); + + it('preserves other keys in the object', () => { + const obj = { oldKey: 'value', anotherKey: 'anotherValue' }; + const result = replaceObjectKeyAt(obj, 'oldKey', 'newKey'); + expect(result).toEqual({ newKey: 'value', anotherKey: 'anotherValue' }); + }); + + it('does not mutate the original object', () => { + const obj = { oldKey: 'value' }; + const result = replaceObjectKeyAt(obj, 'oldKey', 'newKey'); + expect(obj).toEqual({ oldKey: 'value' }); + expect(result).not.toBe(obj); + }); + }); +}); diff --git a/packages/data-widgets/lib/components/object-property.tsx b/packages/data-widgets/lib/components/object-property.tsx index b2a10be..3608937 100644 --- a/packages/data-widgets/lib/components/object-property.tsx +++ b/packages/data-widgets/lib/components/object-property.tsx @@ -22,12 +22,7 @@ import { } from '@chakra-ui/react'; import { CollecticonTag } from '@devseed-ui/collecticons-chakra'; import { useFormikContext } from 'formik'; -import get from 'lodash-es/get'; -import set from 'lodash-es/set'; -import unset from 'lodash-es/unset'; -import mapKeys from 'lodash-es/mapKeys'; -import toPath from 'lodash-es/toPath'; -import cloneDeep from 'lodash-es/cloneDeep'; +import { get, set, unset, mapKeys, toPath, cloneDeep } from 'lodash-es'; const fieldTypes = [ { value: 'string', label: 'String' }, @@ -52,22 +47,16 @@ type FieldTypes = (typeof fieldTypes)[number]['value']; * - 'json' for arrays with mixed types or objects. * - 'string' for all other types. */ -const inferFieldType = (value: any): FieldTypes => { +export const inferFieldType = (value: any): FieldTypes => { if (typeof value === 'number') { return 'number'; } - // if (typeof value === 'boolean') { - // return 'boolean'; - // } - if (Array.isArray(value)) { if (value.every((v) => typeof v === 'number')) { return 'number[]'; } - // if (value.every((v) => typeof v === 'boolean')) { - // return 'boolean[]'; - // } + if (value.every((v) => typeof v === 'string')) { return 'string[]'; } @@ -88,7 +77,7 @@ const inferFieldType = (value: any): FieldTypes => { * 'number[]', or 'json'. * @returns A SchemaField object if the type is recognized, otherwise null. */ -const getFieldSchema = (type: FieldTypes): SchemaField | null => { +export const getFieldSchema = (type: FieldTypes): SchemaField | null => { if (type === 'string') { return { type: 'string', @@ -134,7 +123,7 @@ const getFieldSchema = (type: FieldTypes): SchemaField | null => { * @param newKey - The new key that will replace the old key. * @returns A new object with the key replaced at the specified path. */ -const replaceObjectKeyAt = (obj: any, path: string, newKey: string) => { +export const replaceObjectKeyAt = (obj: any, path: string, newKey: string) => { const parts = toPath(path); const last = parts.pop()!; const isRoot = !parts.length; diff --git a/packages/data-widgets/lib/utils/__snapshots__/utils.test.ts.snap b/packages/data-widgets/lib/utils/__snapshots__/utils.test.ts.snap new file mode 100644 index 0000000..defdfd5 --- /dev/null +++ b/packages/data-widgets/lib/utils/__snapshots__/utils.test.ts.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getArrayLabel should return Item if label is undefined 1`] = ` + + + Item + + + + 01 + + +`; + +exports[`getArrayLabel should return a label with a number suffix for string labels 1`] = ` + + + Label + + + + 10 + + +`; diff --git a/packages/data-widgets/lib/utils/utils.test.ts b/packages/data-widgets/lib/utils/utils.test.ts new file mode 100644 index 0000000..44a2cc4 --- /dev/null +++ b/packages/data-widgets/lib/utils/utils.test.ts @@ -0,0 +1,78 @@ +import { mod, getArrayLabel, toNumber, castArray } from './index'; +import { SchemaField } from '@stac-manager/data-core'; + +describe('mod', () => { + it('should return the correct positive remainder', () => { + expect(mod(5, 3)).toBe(2); + }); + + it('should handle negative dividends correctly', () => { + expect(mod(-1, 3)).toBe(2); + }); + + it('should handle zero divisor correctly', () => { + expect(mod(0, 3)).toBe(0); + }); +}); + +describe('getArrayLabel', () => { + it('should return a label with a number suffix for string labels', () => { + const field: SchemaField = { label: 'Label' } as any; + const result = getArrayLabel(field, 9); + + expect(result).toEqual({ + label: 'Label', + num: 10, // 1-based index + formatted: expect.anything() + }); + expect(result?.formatted).toMatchSnapshot(); + }); + + it('should cycle through array labels based on index', () => { + const field: SchemaField = { label: ['One', 'Two', 'Three'] } as any; + expect(getArrayLabel(field, 0)?.label).toBe('One'); + expect(getArrayLabel(field, 3)?.label).toBe('One'); + expect(getArrayLabel(field, 4)?.label).toBe('Two'); + }); + + it('should return null if label is null', () => { + const field: SchemaField = { label: null } as any; + expect(getArrayLabel(field, 0)).toBeNull(); + }); + + it('should return Item if label is undefined', () => { + const field: SchemaField = { label: undefined } as any; + const result = getArrayLabel(field, 0); + + expect(result).toEqual({ + label: 'Item', + num: 1, + formatted: expect.anything() + }); + expect(result?.formatted).toMatchSnapshot(); + }); +}); + +describe('toNumber', () => { + it('should convert valid numbers correctly', () => { + expect(toNumber('42')).toBe(42); + expect(toNumber(42)).toBe(42); + }); + + it('should return null for invalid numbers', () => { + expect(toNumber('abc')).toBeNull(); + expect(toNumber(undefined)).toBeNull(); + }); +}); + +describe('castArray', () => { + it('should wrap non-array values in an array', () => { + expect(castArray(42)).toEqual([42]); + expect(castArray('test')).toEqual(['test']); + }); + + it('should return the same array if input is already an array', () => { + expect(castArray([42])).toEqual([42]); + expect(castArray(['test'])).toEqual(['test']); + }); +}); From 4dc18327499466d477036f3808cb2752231fe149 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Tue, 1 Apr 2025 15:59:08 +0100 Subject: [PATCH 2/6] Address review --- packages/data-core/lib/components/plugin-box.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/data-core/lib/components/plugin-box.test.tsx b/packages/data-core/lib/components/plugin-box.test.tsx index 2967b35..84a8dc9 100644 --- a/packages/data-core/lib/components/plugin-box.test.tsx +++ b/packages/data-core/lib/components/plugin-box.test.tsx @@ -29,7 +29,7 @@ const renderWithProviders = ( }; describe('PluginBox', () => { - it.only('renders ErrorBox when editSchema is null', () => { + it('renders ErrorBox when editSchema is null', () => { mockPlugin.editSchema.mockReturnValue(null); const { getByTestId } = renderWithProviders( From b8ef23f0dd9c5b4afff06cbff3ee4b5f934701f8 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Tue, 1 Apr 2025 16:11:14 +0100 Subject: [PATCH 3/6] Add checks workflow --- .github/workflows/checks.yml | 124 ++++++++++++++++++ package.json | 3 +- packages/client/package.json | 3 +- .../src/pages/CollectionDetail/index.tsx | 3 +- 4 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/checks.yml diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..59b5c9c --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,124 @@ +# This workflow performs basic checks: +# +# 1. run a preparation step to install and cache node modules +# 2. once prep succeeds, lint and test run in parallel +# +# The checks only run on non-draft Pull Requests. They don't run on the main +# branch prior to deploy. It's recommended to use branch protection to avoid +# pushes straight to 'main'. + +name: Checks + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + prep: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js ${{ env.NODE }} + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - name: Cache node_modules + uses: actions/cache@v4 + id: cache-node-modules + with: + path: node_modules + key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }} + + - name: Install + run: npm install + + lint: + needs: prep + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js ${{ env.NODE }} + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - name: Cache node_modules + uses: actions/cache@v4 + id: cache-node-modules + with: + path: node_modules + key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }} + + - name: Install + run: npm install + + - name: Lint + run: npm run lint + + test: + needs: prep + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js ${{ env.NODE }} + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - name: Cache node_modules + uses: actions/cache@v4 + id: cache-node-modules + with: + path: node_modules + key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }} + + - name: Install + run: npm install + + - name: Test + run: npm run test + + build: + needs: prep + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js ${{ env.NODE }} + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - name: Cache node_modules + uses: actions/cache@v4 + id: cache-node-modules + with: + path: node_modules + key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }} + + - name: Install + run: npm install + + - name: Test + run: npm run all:build \ No newline at end of file diff --git a/package.json b/package.json index 2b4f3ab..5ae6dc6 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "client:stage": "lerna run stage --scope='@stac-manager/client'", "all:build": "lerna run build", "all:clean": "lerna run clean", - "test": "jest" + "test": "jest", + "lint": "lerna run lint" }, "devDependencies": { "@eslint/js": "^9.13.0", diff --git a/packages/client/package.json b/packages/client/package.json index 9ca8e93..5a1e804 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -21,8 +21,7 @@ "build": "npm run clean && NODE_ENV=production node tasks/build.mjs", "stage": "npm run clean && NODE_ENV=staging node tasks/build.mjs", "clean": "rm -rf dist .parcel-cache", - "lint": "yarn lint:scripts", - "lint:scripts": "eslint app/", + "lint": "eslint src/", "ts-check": "yarn tsc --noEmit --skipLibCheck", "test": "jest" }, diff --git a/packages/client/src/pages/CollectionDetail/index.tsx b/packages/client/src/pages/CollectionDetail/index.tsx index 8a959f1..fbf7e1a 100644 --- a/packages/client/src/pages/CollectionDetail/index.tsx +++ b/packages/client/src/pages/CollectionDetail/index.tsx @@ -24,7 +24,8 @@ import { PopoverArrow, PopoverBody, PopoverContent, - ButtonGroup + ButtonGroup, + Button } from '@chakra-ui/react'; import { useCollection, useStacSearch } from '@developmentseed/stac-react'; import { From fb9e45f29d64966e60e0f6d92a80d53a5e0022f6 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Tue, 1 Apr 2025 16:16:43 +0100 Subject: [PATCH 4/6] Adds build step for plugins Ensures plugins are built during the CI checks. --- .github/workflows/checks.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 59b5c9c..4e4d2ee 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -94,6 +94,9 @@ jobs: - name: Install run: npm install + - name: Build plugins + run: npm run plugins:build + - name: Test run: npm run test From 36d69348e958d2f529bddd9f37efc1d2542d12a4 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Tue, 1 Apr 2025 16:27:15 +0100 Subject: [PATCH 5/6] Uses Plugin.HIDDEN for hidden schema check on test --- packages/data-core/lib/components/plugin-box.test.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/data-core/lib/components/plugin-box.test.tsx b/packages/data-core/lib/components/plugin-box.test.tsx index 84a8dc9..f179d22 100644 --- a/packages/data-core/lib/components/plugin-box.test.tsx +++ b/packages/data-core/lib/components/plugin-box.test.tsx @@ -4,6 +4,7 @@ import { Formik } from 'formik'; import { ChakraProvider } from '@chakra-ui/react'; import { PluginBox } from './plugin-box'; +import { Plugin } from '../plugin-utils/plugin'; const mockPlugin = { name: 'TestPlugin', @@ -44,9 +45,9 @@ describe('PluginBox', () => { }); it('renders nothing when editSchema is Plugin.HIDDEN', () => { - mockPlugin.editSchema.mockReturnValue('HIDDEN'); + mockPlugin.editSchema.mockReturnValue(Plugin.HIDDEN); - const { container } = renderWithProviders( + const { container } = render( {}}> {({ field }) =>
{field.label}
} From 12b48b9b2b8a51da493866f5074e0a251b63b7f3 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Wed, 2 Apr 2025 15:53:00 +0100 Subject: [PATCH 6/6] Removes tests for very simple utilities --- packages/data-widgets/lib/utils/utils.test.ts | 40 +------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/packages/data-widgets/lib/utils/utils.test.ts b/packages/data-widgets/lib/utils/utils.test.ts index 44a2cc4..795a18b 100644 --- a/packages/data-widgets/lib/utils/utils.test.ts +++ b/packages/data-widgets/lib/utils/utils.test.ts @@ -1,20 +1,6 @@ -import { mod, getArrayLabel, toNumber, castArray } from './index'; +import { getArrayLabel } from './index'; import { SchemaField } from '@stac-manager/data-core'; -describe('mod', () => { - it('should return the correct positive remainder', () => { - expect(mod(5, 3)).toBe(2); - }); - - it('should handle negative dividends correctly', () => { - expect(mod(-1, 3)).toBe(2); - }); - - it('should handle zero divisor correctly', () => { - expect(mod(0, 3)).toBe(0); - }); -}); - describe('getArrayLabel', () => { it('should return a label with a number suffix for string labels', () => { const field: SchemaField = { label: 'Label' } as any; @@ -52,27 +38,3 @@ describe('getArrayLabel', () => { expect(result?.formatted).toMatchSnapshot(); }); }); - -describe('toNumber', () => { - it('should convert valid numbers correctly', () => { - expect(toNumber('42')).toBe(42); - expect(toNumber(42)).toBe(42); - }); - - it('should return null for invalid numbers', () => { - expect(toNumber('abc')).toBeNull(); - expect(toNumber(undefined)).toBeNull(); - }); -}); - -describe('castArray', () => { - it('should wrap non-array values in an array', () => { - expect(castArray(42)).toEqual([42]); - expect(castArray('test')).toEqual(['test']); - }); - - it('should return the same array if input is already an array', () => { - expect(castArray([42])).toEqual([42]); - expect(castArray(['test'])).toEqual(['test']); - }); -});