Skip to content
Snippets Groups Projects
Commit e0ef403b authored by Miłosz Grocholewski's avatar Miłosz Grocholewski
Browse files

Merge branch 'feat/MIN-100-add-test-tests' into 'development'

feat(layer-text): add tests for adding text functionality

Closes MIN-100

See merge request !366
parents a90a9d98 08bad546
No related branches found
No related tags found
1 merge request!366feat(layer-text): add tests for adding text functionality
Pipeline #100343 passed
Showing
with 591 additions and 12 deletions
/* eslint-disable no-magic-numbers */
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { StoreType } from '@/redux/store';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
import { GLYPHS_STATE_INITIAL_MOCK } from '@/redux/glyphs/glyphs.mock';
import { apiPath } from '@/redux/apiPath';
import { HttpStatusCode } from 'axios';
import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
import {
LAYER_STATE_DEFAULT_DATA,
LAYERS_STATE_INITIAL_LAYER_MOCK,
} from '@/redux/layers/layers.mock';
import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
import { overlayFixture } from '@/models/fixtures/overlaysFixture';
import { showToast } from '@/utils/showToast';
import {
TEXT_FONT_SIZES,
TEXT_HORIZONTAL_ALIGNMENTS,
TEXT_VERTICAL_ALIGNMENTS,
} from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactory.constants';
import { layerTextFixture } from '@/models/fixtures/layerTextFixture';
import { LayerTextFactoryModal } from './LayerTextFactoryModal.component';
const mockedAxiosNewClient = mockNetworkNewAPIResponse();
const glyph = { id: 1, file: 23, filename: 'Glyph1.png' };
jest.mock('../../../../utils/showToast');
const renderComponent = (): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore({
...INITIAL_STORE_STATE_MOCK,
glyphs: {
...GLYPHS_STATE_INITIAL_MOCK,
data: [glyph],
},
layers: {
0: {
...LAYERS_STATE_INITIAL_LAYER_MOCK,
data: {
...LAYER_STATE_DEFAULT_DATA,
activeLayer: 1,
},
},
},
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: {
x: 1,
y: 1,
width: 1,
height: 1,
},
},
models: {
...MODELS_DATA_MOCK_WITH_MAIN_MAP,
},
});
return {
store,
...render(
<Wrapper>
<LayerTextFactoryModal />
</Wrapper>,
),
};
};
describe('LayerTextFactoryModal - component', () => {
it('should render LayerTextFactoryModal component with initial state', () => {
renderComponent();
expect(screen.getByText('Font size:')).toBeInTheDocument();
expect(screen.getByText('Horizontal alignment:')).toBeInTheDocument();
expect(screen.getByText('Vertical alignment:')).toBeInTheDocument();
expect(screen.getByText('Color:')).toBeInTheDocument();
expect(screen.getByText('Border color:')).toBeInTheDocument();
});
it('should display a list of fontSizes in the dropdown', async () => {
renderComponent();
const dropdown = screen.getByTestId('font-size-select');
if (!dropdown.firstChild) {
throw new Error('Dropdown does not have a firstChild');
}
fireEvent.click(dropdown.firstChild);
await waitFor(() => expect(screen.getByText(TEXT_FONT_SIZES[0])).toBeInTheDocument());
fireEvent.click(screen.getByText(TEXT_FONT_SIZES[0]));
});
it('should display a list of text horizontal alignments in the dropdown', async () => {
renderComponent();
const dropdown = screen.getByTestId('horizontal-alignment-select');
if (!dropdown.firstChild) {
throw new Error('Dropdown does not have a firstChild');
}
fireEvent.click(dropdown.firstChild);
await waitFor(() => {
const listItem = screen.getByRole('option', { name: TEXT_HORIZONTAL_ALIGNMENTS[0].name });
return expect(listItem).toBeInTheDocument();
});
});
it('should display a list of text vertical alignments in the dropdown', async () => {
renderComponent();
const dropdown = screen.getByTestId('vertical-alignment-select');
if (!dropdown.firstChild) {
throw new Error('Dropdown does not have a firstChild');
}
fireEvent.click(dropdown.firstChild);
await waitFor(() => {
const listItem = screen.getByRole('option', { name: TEXT_VERTICAL_ALIGNMENTS[0].name });
return expect(listItem).toBeInTheDocument();
});
});
it('should handle form submission correctly', async () => {
mockedAxiosNewClient
.onPost(apiPath.addLayerText(0, 1))
.reply(HttpStatusCode.Ok, layerTextFixture);
renderComponent();
const submitButton = screen.getByText(/Submit/i);
await act(async () => {
fireEvent.click(submitButton);
});
expect(showToast).toHaveBeenCalledWith({
message: 'A new text has been successfully added',
type: 'success',
});
});
});
...@@ -28,7 +28,7 @@ import { layerAddText } from '@/redux/layers/layers.slice'; ...@@ -28,7 +28,7 @@ import { layerAddText } from '@/redux/layers/layers.slice';
import drawElementOnLayer from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/utils/drawElementOnLayer'; import drawElementOnLayer from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/utils/drawElementOnLayer';
import { useMapInstance } from '@/utils/context/mapInstanceContext'; import { useMapInstance } from '@/utils/context/mapInstanceContext';
export const LayerTextFactoryModalComponent: React.FC = () => { export const LayerTextFactoryModal: React.FC = () => {
const activeLayer = useAppSelector(layersActiveLayerSelector); const activeLayer = useAppSelector(layersActiveLayerSelector);
const currentModelId = useAppSelector(currentModelIdSelector); const currentModelId = useAppSelector(currentModelIdSelector);
const layerTextFactoryState = useAppSelector(layerTextFactoryStateSelector); const layerTextFactoryState = useAppSelector(layerTextFactoryStateSelector);
......
...@@ -8,7 +8,7 @@ import { ...@@ -8,7 +8,7 @@ import {
TEXT_VERTICAL_ALIGNMENTS, TEXT_VERTICAL_ALIGNMENTS,
} from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactory.constants'; } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactory.constants';
import { LayerTextFactoryForm } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactory.types'; import { LayerTextFactoryForm } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactory.types';
import ColorTilePicker from '@/shared/ColorPicker/ColorTilePicker.component'; import { ColorTilePicker } from '@/shared/ColorPicker/ColorTilePicker.component';
import hexToRgbIntAlpha from '@/utils/convert/hexToRgbIntAlpha'; import hexToRgbIntAlpha from '@/utils/convert/hexToRgbIntAlpha';
import { Color } from '@/types/models'; import { Color } from '@/types/models';
...@@ -34,6 +34,7 @@ export const LayerTextForm = ({ data, onChange }: LayerTextFormProps): React.JSX ...@@ -34,6 +34,7 @@ export const LayerTextForm = ({ data, onChange }: LayerTextFormProps): React.JSX
options={fontSizes} options={fontSizes}
selectedId={data.fontSize} selectedId={data.fontSize}
listClassName="max-h-48" listClassName="max-h-48"
testId="font-size-select"
onChange={value => onChange(value, 'fontSize')} onChange={value => onChange(value, 'fontSize')}
/> />
</div> </div>
...@@ -42,6 +43,7 @@ export const LayerTextForm = ({ data, onChange }: LayerTextFormProps): React.JSX ...@@ -42,6 +43,7 @@ export const LayerTextForm = ({ data, onChange }: LayerTextFormProps): React.JSX
<Select <Select
options={TEXT_HORIZONTAL_ALIGNMENTS} options={TEXT_HORIZONTAL_ALIGNMENTS}
selectedId={data.horizontalAlign} selectedId={data.horizontalAlign}
testId="horizontal-alignment-select"
onChange={value => onChange(value, 'horizontalAlign')} onChange={value => onChange(value, 'horizontalAlign')}
/> />
</div> </div>
...@@ -50,6 +52,7 @@ export const LayerTextForm = ({ data, onChange }: LayerTextFormProps): React.JSX ...@@ -50,6 +52,7 @@ export const LayerTextForm = ({ data, onChange }: LayerTextFormProps): React.JSX
<Select <Select
options={TEXT_VERTICAL_ALIGNMENTS} options={TEXT_VERTICAL_ALIGNMENTS}
selectedId={data.verticalAlign} selectedId={data.verticalAlign}
testId="vertical-alignment-select"
onChange={value => onChange(value, 'verticalAlign')} onChange={value => onChange(value, 'verticalAlign')}
/> />
</div> </div>
......
...@@ -9,7 +9,7 @@ import { ...@@ -9,7 +9,7 @@ import {
LayerImageObjectEditFactoryModal, LayerImageObjectEditFactoryModal,
LayerImageObjectFactoryModal, LayerImageObjectFactoryModal,
} from '@/components/FunctionalArea/Modal/LayerImageObjectModal'; } from '@/components/FunctionalArea/Modal/LayerImageObjectModal';
import { LayerTextFactoryModalComponent } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component'; import { LayerTextFactoryModal } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component';
import { EditOverlayModal } from './EditOverlayModal'; import { EditOverlayModal } from './EditOverlayModal';
import { LoginModal } from './LoginModal'; import { LoginModal } from './LoginModal';
import { ErrorReportModal } from './ErrorReportModal'; import { ErrorReportModal } from './ErrorReportModal';
...@@ -102,7 +102,7 @@ export const Modal = (): React.ReactNode => { ...@@ -102,7 +102,7 @@ export const Modal = (): React.ReactNode => {
)} )}
{isOpen && modalName === 'layer-text-factory' && ( {isOpen && modalName === 'layer-text-factory' && (
<ModalLayout> <ModalLayout>
<LayerTextFactoryModalComponent /> <LayerTextFactoryModal />
</ModalLayout> </ModalLayout>
)} )}
</> </>
......
/* eslint-disable no-magic-numbers */
import VectorLayer from 'ol/layer/Vector';
import { Feature } from 'ol';
import Map from 'ol/Map';
import { layerImageFixture } from '@/models/fixtures/layerImageFixture';
import { layerTextFixture } from '@/models/fixtures/layerTextFixture';
import drawElementOnLayer from './drawElementOnLayer';
describe('drawElementOnLayer', () => {
let mockMapInstance: Map;
let mockLayer: VectorLayer;
let mockFeature: Feature;
let drawImageMock: () => void;
let drawTextMock: () => void;
beforeEach(() => {
mockFeature = new Feature();
mockFeature.setId(123);
drawImageMock = jest.fn(() => {});
drawTextMock = jest.fn(() => {});
mockLayer = new VectorLayer();
jest.spyOn(mockLayer, 'get').mockImplementation(key => {
if (key === 'id') {
return 1;
}
if (key === 'drawImage') {
return drawImageMock;
}
if (key === 'drawText') {
return drawTextMock;
}
return undefined;
});
const dummyElement = document.createElement('div');
mockMapInstance = new Map({ target: dummyElement });
jest.spyOn(mockMapInstance, 'getAllLayers').mockImplementation(() => [mockLayer]);
});
it('should call the drawImage function to draw a new image on the specified layer', () => {
drawElementOnLayer({
mapInstance: mockMapInstance,
activeLayer: 1,
object: layerImageFixture,
drawFunctionKey: 'drawImage',
});
expect(mockLayer.get).toHaveBeenCalledTimes(2);
expect(drawImageMock).toHaveBeenCalledWith(layerImageFixture);
});
it('should call the drawText function to draw a new text object on the specified layer', () => {
drawElementOnLayer({
mapInstance: mockMapInstance,
activeLayer: 1,
object: layerTextFixture,
drawFunctionKey: 'drawText',
});
expect(mockLayer.get).toHaveBeenCalledTimes(2);
expect(drawTextMock).toHaveBeenCalledWith(layerTextFixture);
});
it('should not invoke drawing functions when the activeLayer does not match the specified layer', () => {
drawElementOnLayer({
mapInstance: mockMapInstance,
activeLayer: 2,
object: layerTextFixture,
drawFunctionKey: 'drawText',
});
expect(mockLayer.get).toHaveBeenCalledTimes(1);
expect(drawTextMock).not.toHaveBeenCalled();
expect(drawImageMock).not.toHaveBeenCalled();
});
});
...@@ -15,7 +15,7 @@ export default function drawElementOnLayer({ ...@@ -15,7 +15,7 @@ export default function drawElementOnLayer({
mapInstance?.getAllLayers().forEach(layer => { mapInstance?.getAllLayers().forEach(layer => {
if (layer.get('id') === activeLayer) { if (layer.get('id') === activeLayer) {
const drawObject = layer.get(drawFunctionKey); const drawObject = layer.get(drawFunctionKey);
if (drawObject instanceof Function) { if (drawObject instanceof Function || typeof drawObject === 'function') {
drawObject(object); drawObject(object);
} }
} }
......
import { ZOD_SEED } from '@/constants';
// eslint-disable-next-line import/no-extraneous-dependencies
import { createFixture } from 'zod-fixture';
import { layerTextSchema } from '@/models/layerTextSchema';
export const layerTextFixture = createFixture(layerTextSchema, {
seed: ZOD_SEED,
array: { min: 1, max: 1 },
});
...@@ -15,13 +15,48 @@ import { layerRectsFixture } from '@/models/fixtures/layerRectsFixture'; ...@@ -15,13 +15,48 @@ import { layerRectsFixture } from '@/models/fixtures/layerRectsFixture';
import { layerOvalsFixture } from '@/models/fixtures/layerOvalsFixture'; import { layerOvalsFixture } from '@/models/fixtures/layerOvalsFixture';
import { layerLinesFixture } from '@/models/fixtures/layerLinesFixture'; import { layerLinesFixture } from '@/models/fixtures/layerLinesFixture';
import { layerImagesFixture } from '@/models/fixtures/layerImagesFixture'; import { layerImagesFixture } from '@/models/fixtures/layerImagesFixture';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { layerFixture } from '@/models/fixtures/layerFixture';
import { DEFAULT_ERROR } from '@/constants/errors';
import { layerImageFixture } from '@/models/fixtures/layerImageFixture';
import { layerTextFixture } from '@/models/fixtures/layerTextFixture';
import layersReducer, {
layerAddImage,
layerAddText,
layerDeleteImage,
layerUpdateImage,
setActiveLayer,
setLayerVisibility,
} from './layers.slice';
import { LayersState } from './layers.types'; import { LayersState } from './layers.types';
import layersReducer from './layers.slice';
const mockedAxiosClient = mockNetworkNewAPIResponse(); const mockedAxiosClient = mockNetworkNewAPIResponse();
const INITIAL_STATE: LayersState = LAYERS_STATE_INITIAL_MOCK; const INITIAL_STATE: LayersState = LAYERS_STATE_INITIAL_MOCK;
const layersState: LayersState = {
1: {
data: {
layersVisibility: {
[layerFixture.id]: true,
},
layers: [
{
details: layerFixture,
texts: {},
rects: [],
ovals: [],
lines: [],
images: {},
},
],
activeLayer: layerFixture.id,
},
loading: 'idle',
error: DEFAULT_ERROR,
},
};
describe('layers reducer', () => { describe('layers reducer', () => {
let store = {} as ToolkitStoreWithSingleSlice<LayersState>; let store = {} as ToolkitStoreWithSingleSlice<LayersState>;
beforeEach(() => { beforeEach(() => {
...@@ -140,4 +175,70 @@ describe('layers reducer', () => { ...@@ -140,4 +175,70 @@ describe('layers reducer', () => {
expect(promiseFulfilled).toEqual('succeeded'); expect(promiseFulfilled).toEqual('succeeded');
}); });
}); });
it('should handle setLayerVisibilityReducer', () => {
const { store: layersStore } = getReduxWrapperWithStore({
layers: layersState,
});
layersStore.dispatch(
setLayerVisibility({ modelId: 1, layerId: layerFixture.id, visible: false }),
);
expect(layersStore.getState().layers[1].data?.layersVisibility[layerFixture.id]).toBe(false);
});
it('should handle setActiveLayerReducer', () => {
const { store: layersStore } = getReduxWrapperWithStore({
layers: layersState,
});
layersStore.dispatch(setActiveLayer({ modelId: 1, layerId: layerFixture.id }));
expect(layersStore.getState().layers[1].data?.activeLayer).toBe(layerFixture.id);
});
it('should handle layerAddImageReducer', () => {
const { store: layersStore } = getReduxWrapperWithStore({
layers: layersState,
});
layersStore.dispatch(
layerAddImage({ modelId: 1, layerId: layerFixture.id, layerImage: layerImageFixture }),
);
expect(layersStore.getState().layers[1].data?.layers[0].images[layerImageFixture.id]).toEqual(
layerImageFixture,
);
});
it('should handle layerUpdateImageReducer', () => {
const { store: layersStore } = getReduxWrapperWithStore({
layers: layersState,
});
layersStore.dispatch(
layerUpdateImage({ modelId: 1, layerId: layerFixture.id, layerImage: layerImageFixture }),
);
expect(layersStore.getState().layers[1].data?.layers[0].images[layerImageFixture.id]).toEqual(
layerImageFixture,
);
});
it('should handle layerDeleteImageReducer', () => {
const { store: layersStore } = getReduxWrapperWithStore({
layers: layersState,
});
layersStore.dispatch(
layerDeleteImage({ modelId: 1, layerId: layerFixture.id, imageId: layerImageFixture.id }),
);
expect(
layersStore.getState().layers[1].data?.layers[0].images[layerImageFixture.id],
).toBeUndefined();
});
it('should handle layerAddTextReducer', () => {
const { store: layersStore } = getReduxWrapperWithStore({
layers: layersState,
});
layersStore.dispatch(
layerAddText({ modelId: 1, layerId: layerFixture.id, layerText: layerTextFixture }),
);
expect(layersStore.getState().layers[1].data?.layers[0].texts[layerTextFixture.id]).toEqual(
layerTextFixture,
);
});
}); });
...@@ -9,10 +9,14 @@ import { HttpStatusCode } from 'axios'; ...@@ -9,10 +9,14 @@ import { HttpStatusCode } from 'axios';
import { LayersState } from '@/redux/layers/layers.types'; import { LayersState } from '@/redux/layers/layers.types';
import { import {
addLayerForModel, addLayerForModel,
addLayerImageObject,
addLayerText,
getLayer, getLayer,
getLayersForModel, getLayersForModel,
removeLayer, removeLayer,
removeLayerImage,
updateLayer, updateLayer,
updateLayerImageObject,
} from '@/redux/layers/layers.thunks'; } from '@/redux/layers/layers.thunks';
import { layersFixture } from '@/models/fixtures/layersFixture'; import { layersFixture } from '@/models/fixtures/layersFixture';
import { layerTextsFixture } from '@/models/fixtures/layerTextsFixture'; import { layerTextsFixture } from '@/models/fixtures/layerTextsFixture';
...@@ -21,6 +25,9 @@ import { layerOvalsFixture } from '@/models/fixtures/layerOvalsFixture'; ...@@ -21,6 +25,9 @@ import { layerOvalsFixture } from '@/models/fixtures/layerOvalsFixture';
import { layerLinesFixture } from '@/models/fixtures/layerLinesFixture'; import { layerLinesFixture } from '@/models/fixtures/layerLinesFixture';
import { layerFixture } from '@/models/fixtures/layerFixture'; import { layerFixture } from '@/models/fixtures/layerFixture';
import { layerImagesFixture } from '@/models/fixtures/layerImagesFixture'; import { layerImagesFixture } from '@/models/fixtures/layerImagesFixture';
import { layerImageFixture } from '@/models/fixtures/layerImageFixture';
import { layerTextFixture } from '@/models/fixtures/layerTextFixture';
import { BLACK_COLOR } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
import layersReducer from './layers.slice'; import layersReducer from './layers.slice';
const mockedAxiosClient = mockNetworkNewAPIResponse(); const mockedAxiosClient = mockNetworkNewAPIResponse();
...@@ -159,4 +166,167 @@ describe('layers thunks', () => { ...@@ -159,4 +166,167 @@ describe('layers thunks', () => {
expect(result.meta.requestStatus).toBe('fulfilled'); expect(result.meta.requestStatus).toBe('fulfilled');
}); });
}); });
describe('addLayerImageObject', () => {
it('should successfully add a new layer image object', async () => {
mockedAxiosClient
.onPost(apiPath.addLayerImageObject(1, 2))
.reply(HttpStatusCode.Ok, layerImageFixture);
const result = await store.dispatch(
addLayerImageObject({
modelId: 1,
layerId: 2,
x: 1,
y: 1,
z: 1,
width: 1,
height: 1,
glyph: 1,
}),
);
expect(result.meta.requestStatus).toBe('fulfilled');
expect(result.payload).toEqual(layerImageFixture);
});
it('should return null when response data is invalid', async () => {
mockedAxiosClient
.onPost(apiPath.addLayerImageObject(1, 2))
.reply(HttpStatusCode.Ok, { invalid: 'data' });
const result = await store.dispatch(
addLayerImageObject({
modelId: 1,
layerId: 2,
x: 1,
y: 1,
z: 1,
width: 1,
height: 1,
glyph: 1,
}),
);
expect(result.payload).toBeNull();
});
});
describe('updateLayerImageObject', () => {
it('should successfully update the layer image object', async () => {
mockedAxiosClient
.onPut(apiPath.updateLayerImageObject(1, 2, 1))
.reply(HttpStatusCode.Ok, layerImageFixture);
const result = await store.dispatch(
updateLayerImageObject({
modelId: 1,
layerId: 2,
id: 1,
x: 1,
y: 1,
z: 1,
width: 1,
height: 1,
glyph: 1,
}),
);
expect(result.meta.requestStatus).toBe('fulfilled');
expect(result.payload).toEqual(layerImageFixture);
});
it('should return null when response data is invalid', async () => {
mockedAxiosClient
.onPut(apiPath.updateLayerImageObject(1, 2, 1))
.reply(HttpStatusCode.Ok, { invalid: 'data' });
const result = await store.dispatch(
updateLayerImageObject({
modelId: 1,
layerId: 2,
id: 1,
x: 1,
y: 1,
z: 1,
width: 1,
height: 1,
glyph: 1,
}),
);
expect(result.payload).toBeNull();
});
});
describe('removeLayerImage', () => {
it('should successfully remove the layer image', async () => {
mockedAxiosClient
.onDelete(apiPath.removeLayerImageObject(1, 2, 1))
.reply(HttpStatusCode.NoContent);
const result = await store.dispatch(removeLayerImage({ modelId: 1, layerId: 2, imageId: 1 }));
expect(result.meta.requestStatus).toBe('fulfilled');
});
});
describe('addLayerText', () => {
it('should successfully add a new layer text', async () => {
mockedAxiosClient
.onPost(apiPath.addLayerText(1, 2))
.reply(HttpStatusCode.Ok, layerTextFixture);
const result = await store.dispatch(
addLayerText({
modelId: 1,
layerId: 2,
z: 1,
boundingBox: {
width: 1,
height: 1,
x: 1,
y: 1,
},
textData: {
notes: '123',
fontSize: 12,
horizontalAlign: 'LEFT',
verticalAlign: 'TOP',
color: BLACK_COLOR,
borderColor: BLACK_COLOR,
},
}),
);
expect(result.meta.requestStatus).toBe('fulfilled');
expect(result.payload).toEqual(layerTextFixture);
});
it('should return null when response data is invalid', async () => {
mockedAxiosClient
.onPost(apiPath.addLayerText(1, 2))
.reply(HttpStatusCode.Ok, { invalid: 'data' });
const result = await store.dispatch(
addLayerText({
modelId: 1,
layerId: 2,
z: 1,
boundingBox: {
width: 1,
height: 1,
x: 1,
y: 1,
},
textData: {
notes: '123',
fontSize: 12,
horizontalAlign: 'LEFT',
verticalAlign: 'TOP',
color: BLACK_COLOR,
borderColor: BLACK_COLOR,
},
}),
);
expect(result.payload).toBeNull();
});
});
}); });
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import { ColorTilePicker } from './ColorTilePicker.component';
describe('ColorTilePicker', () => {
it('renders with the initial color', () => {
render(<ColorTilePicker initialColor="#00ff00" colorChange={jest.fn()} />);
const colorTile = screen.getByRole('button');
expect(colorTile).toHaveStyle({ backgroundColor: '#00ff00' });
});
it('toggles color picker visibility on click', () => {
render(<ColorTilePicker initialColor="#000000" colorChange={jest.fn()} />);
const colorTile = screen.getByRole('button');
expect(screen.queryByTestId('color-picker')).not.toBeInTheDocument();
fireEvent.click(colorTile);
expect(screen.getByTestId('color-picker')).toBeInTheDocument();
fireEvent.click(colorTile);
expect(screen.queryByTestId('color-picker')).not.toBeInTheDocument();
});
it('closes color picker when clicking outside', () => {
render(<ColorTilePicker initialColor="#000000" colorChange={jest.fn()} />);
const colorTile = screen.getByRole('button');
fireEvent.click(colorTile);
expect(screen.getByTestId('color-picker')).toBeInTheDocument();
fireEvent.mouseDown(document);
expect(screen.queryByTestId('color-picker')).not.toBeInTheDocument();
});
it('handles keyboard interaction (Enter key)', () => {
render(<ColorTilePicker initialColor="#000000" colorChange={jest.fn()} />);
const colorTile = screen.getByRole('button');
expect(screen.queryByTestId('color-picker')).not.toBeInTheDocument();
fireEvent.keyDown(colorTile, { key: 'Enter', code: 'Enter', charCode: 13 });
expect(screen.getByTestId('color-picker')).toBeInTheDocument();
fireEvent.keyDown(colorTile, { key: 'Enter', code: 'Enter', charCode: 13 });
expect(screen.queryByTestId('color-picker')).not.toBeInTheDocument();
});
});
...@@ -6,12 +6,14 @@ type ColorTilePickerProps = { ...@@ -6,12 +6,14 @@ type ColorTilePickerProps = {
initialColor?: string; initialColor?: string;
colorChange: (color: string) => void; colorChange: (color: string) => void;
height?: string; height?: string;
testId?: string;
}; };
const ColorTilePicker: React.FC<ColorTilePickerProps> = ({ export const ColorTilePicker: React.FC<ColorTilePickerProps> = ({
initialColor, initialColor,
colorChange, colorChange,
height = '40px', height = '40px',
testId = 'color-tile-picker',
}) => { }) => {
const [color, setColor] = useState<string>(initialColor || '#000000'); const [color, setColor] = useState<string>(initialColor || '#000000');
const [visible, setVisible] = useState<boolean>(false); const [visible, setVisible] = useState<boolean>(false);
...@@ -43,7 +45,7 @@ const ColorTilePicker: React.FC<ColorTilePickerProps> = ({ ...@@ -43,7 +45,7 @@ const ColorTilePicker: React.FC<ColorTilePickerProps> = ({
}, []); }, []);
return ( return (
<div style={{ position: 'relative' }}> <div data-testid={testId} style={{ position: 'relative' }}>
<div <div
className="w-full cursor-pointer rounded shadow-primary" className="w-full cursor-pointer rounded shadow-primary"
role="button" role="button"
...@@ -56,12 +58,10 @@ const ColorTilePicker: React.FC<ColorTilePickerProps> = ({ ...@@ -56,12 +58,10 @@ const ColorTilePicker: React.FC<ColorTilePickerProps> = ({
}} }}
/> />
{visible && ( {visible && (
<div ref={pickerRef} className="absolute right-full top-0"> <div ref={pickerRef} className="absolute right-full top-0" data-testid="color-picker">
<ColorPicker value={color} onChange={handleChange} /> <ColorPicker value={color} onChange={handleChange} />
</div> </div>
)} )}
</div> </div>
); );
}; };
export default ColorTilePicker;
export { ColorTilePicker } from './ColorTilePicker.component';
...@@ -9,6 +9,7 @@ type SelectProps = { ...@@ -9,6 +9,7 @@ type SelectProps = {
onChange: (selectedId: number | string) => void; onChange: (selectedId: number | string) => void;
width?: string | number; width?: string | number;
listClassName?: string; listClassName?: string;
testId?: string;
}; };
export const Select = ({ export const Select = ({
...@@ -17,6 +18,7 @@ export const Select = ({ ...@@ -17,6 +18,7 @@ export const Select = ({
onChange, onChange,
width = '100%', width = '100%',
listClassName = '', listClassName = '',
testId = 'select-component',
}: SelectProps): React.JSX.Element => { }: SelectProps): React.JSX.Element => {
const selectedOption = options.find(option => option.id === selectedId) || null; const selectedOption = options.find(option => option.id === selectedId) || null;
...@@ -42,7 +44,7 @@ export const Select = ({ ...@@ -42,7 +44,7 @@ export const Select = ({
return ( return (
<div <div
data-testid="select-component" data-testid={testId}
className={twMerge( className={twMerge(
'relative rounded-t bg-white text-xs shadow-primary', 'relative rounded-t bg-white text-xs shadow-primary',
!isOpen && 'rounded-b', !isOpen && 'rounded-b',
......
import hexToRgbIntAlpha from './hexToRgbIntAlpha';
describe('hexToRgbIntAlpha', () => {
it('should convert a valid 6-character HEX color to RGB integer and alpha', () => {
const result = hexToRgbIntAlpha('#ff5733');
expect(result).toEqual({ rgb: 16734003, alpha: 1 });
});
it('should convert a valid 8-character HEX color to RGB integer and alpha', () => {
const result = hexToRgbIntAlpha('#ff5733cc');
expect(result).toEqual({ rgb: 16734003, alpha: 0.8 });
});
it('should throw an error for an invalid HEX color length', () => {
expect(() => hexToRgbIntAlpha('#f5')).toThrow('Invalid HEX color. Must be 6 or 8 characters.');
});
it('should throw an error for a malformed HEX color', () => {
expect(() => hexToRgbIntAlpha('not-a-hex')).toThrow(
'Invalid HEX color. Must be 6 or 8 characters.',
);
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment