Added some tests + fix somebugs + allow default config to App when fetch is not available #17

Merged
Siklos merged 11 commits from dev.tests into dev 2022-08-07 10:08:22 -04:00
7 changed files with 439 additions and 13 deletions

View file

@ -1,5 +1,9 @@
# SVG Layout Designer React
[![Build Status](https://drone.siklos-chaneru.duckdns.org/api/badges/Siklos/svg-layout-designer-react/status.svg)](https://drone.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react)
[![Build Status](https://drone.siklos-chaneru.duckdns.org/api/badges/Siklos/svg-layout-designer-react/status.svg?ref=refs/heads/dev)](https://drone.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react)
An svg layout designer.
# Getting Started

View file

@ -100,8 +100,39 @@ export class App extends React.Component<IAppProps> {
historyCurrentStep: 0,
isLoaded: true
});
}, (error) => { throw new Error(error); }
);
}, (error) => {
// TODO: Implement an alert component
console.warn('[NewEditor] Could not fetch resource from API. Returning default.', error);
const MainContainer = new ContainerModel(
null,
{
id: 'main',
parentId: 'null',
x: 0,
y: 0,
width: DEFAULT_CONFIG.MainContainer.Width,
height: DEFAULT_CONFIG.MainContainer.Height,
fillOpacity: DEFAULT_CONFIG.MainContainer.Style.fillOpacity,
stroke: DEFAULT_CONFIG.MainContainer.Style.stroke,
}
);
// Save the configuration and the new MainContainer
// and default the selected container to it
this.setState({
configuration: DEFAULT_CONFIG,
history:
[
{
MainContainer,
SelectedContainer: MainContainer,
TypeCounters: {}
}
],
historyCurrentStep: 0,
isLoaded: true
});
});
}
public LoadEditor(files: FileList | null): void {
@ -182,3 +213,28 @@ export async function fetchConfiguration(): Promise<Configuration> {
xhr.send();
});
}
const DEFAULT_CONFIG: Configuration = {
AvailableContainers: [
{
Type: 'Container',
Width: 75,
Height: 100,
Style: {
fillOpacity: 0,
stroke: 'green'
}
}
],
AvailableSymbols: [],
MainContainer: {
Type: 'Container',
Width: 2000,
Height: 100,
Style: {
fillOpacity: 0,
stroke: 'black'
}
}
}

View file

@ -0,0 +1,242 @@
import { describe, test, expect, vi } from 'vitest';
import * as React from 'react';
import { fireEvent, render, screen } from '../../utils/test-utils';
import { ElementsSidebar } from './ElementsSidebar';
import { IContainerModel } from '../../Interfaces/ContainerModel';
describe.concurrent('Elements sidebar', () => {
it('No elements', () => {
render(<ElementsSidebar
MainContainer={null}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={null}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={() => {}}
/>);
expect(screen.getByText(/Elements/i));
expect(screen.queryByText('id')).toBeNull();
expect(screen.queryByText(/main/i)).toBeNull();
});
it('With a MainContainer', () => {
render(<ElementsSidebar
MainContainer={{
children: [],
parent: null,
properties: {
id: 'main',
parentId: null,
x: 0,
y: 0,
width: 2000,
height: 100
},
userData: {}
}}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={null}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={() => {}}
/>);
expect(screen.getByText(/Elements/i));
expect(screen.queryByText('id')).toBeNull();
expect(screen.getByText(/main/i));
});
it('With a selected MainContainer', () => {
const MainContainer = {
children: [],
parent: null,
properties: {
id: 'main',
parentId: '',
x: 0,
y: 0,
width: 2000,
height: 100
},
userData: {}
};
const { container } = render(<ElementsSidebar
MainContainer={MainContainer}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={MainContainer}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={() => {}}
/>);
expect(screen.getByText(/Elements/i));
expect(screen.getByText(/main/i));
expect(screen.queryByText('id')).toBeDefined();
expect(screen.queryByText('parentId')).toBeDefined();
expect(screen.queryByText('x')).toBeDefined();
expect(screen.queryByText('y')).toBeDefined();
expect(screen.queryByText('width')).toBeDefined();
expect(screen.queryByText('height')).toBeDefined();
const propertyId = container.querySelector('#property-id');
const propertyParentId = container.querySelector('#property-parentId');
const propertyX = container.querySelector('#property-x');
const propertyY = container.querySelector('#property-y');
const propertyWidth = container.querySelector('#property-width');
const propertyHeight = container.querySelector('#property-height');
expect((propertyId as HTMLInputElement).value).toBe(MainContainer.properties.id.toString());
expect(propertyParentId).toBeDefined();
expect((propertyParentId as HTMLInputElement).value).toBe('');
expect(propertyX).toBeDefined();
expect((propertyX as HTMLInputElement).value).toBe(MainContainer.properties.x.toString());
expect(propertyY).toBeDefined();
expect((propertyY as HTMLInputElement).value).toBe(MainContainer.properties.y.toString());
expect(propertyWidth).toBeDefined();
expect((propertyWidth as HTMLInputElement).value).toBe(MainContainer.properties.width.toString());
expect(propertyHeight).toBeDefined();
expect((propertyHeight as HTMLInputElement).value).toBe(MainContainer.properties.height.toString());
});
it('With multiple containers', () => {
const children: IContainerModel[] = [];
const MainContainer = {
children,
parent: null,
properties: {
id: 'main',
parentId: '',
x: 0,
y: 0,
width: 2000,
height: 100
},
userData: {}
};
children.push(
{
children: [],
parent: MainContainer,
properties: {
id: 'child-1',
parentId: 'main',
x: 0,
y: 0,
width: 0,
height: 0
},
userData: {}
}
);
children.push(
{
children: [],
parent: MainContainer,
properties: {
id: 'child-2',
parentId: 'main',
x: 0,
y: 0,
width: 0,
height: 0
},
userData: {}
}
);
render(<ElementsSidebar
MainContainer={MainContainer}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={MainContainer}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={() => {}}
/>);
expect(screen.getByText(/Elements/i));
expect(screen.queryByText('id')).toBeDefined();
expect(screen.getByText(/main/i));
expect(screen.getByText(/child-1/i));
expect(screen.getByText(/child-2/i));
});
it('With multiple containers, change selection', () => {
const children: IContainerModel[] = [];
const MainContainer: IContainerModel = {
children,
parent: null,
properties: {
id: 'main',
parentId: '',
x: 0,
y: 0,
width: 2000,
height: 100
},
userData: {}
};
const child1Model: IContainerModel = {
children: [],
parent: MainContainer,
properties: {
id: 'child-1',
parentId: 'main',
x: 0,
y: 0,
width: 0,
height: 0
},
userData: {}
};
children.push(child1Model);
let SelectedContainer = MainContainer;
const selectContainer = vi.fn((container: IContainerModel) => {
SelectedContainer = container;
});
const { container, rerender } = render(<ElementsSidebar
MainContainer={MainContainer}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={SelectedContainer}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={selectContainer}
/>);
expect(screen.getByText(/Elements/i));
expect(screen.queryByText('id')).toBeDefined();
expect(screen.getByText(/main/i));
const child1 = screen.getByText(/child-1/i);
expect(child1);
const propertyId = container.querySelector('#property-id');
const propertyParentId = container.querySelector('#property-parentId');
expect((propertyId as HTMLInputElement).value).toBe(MainContainer.properties.id.toString());
expect((propertyParentId as HTMLInputElement).value).toBe('');
fireEvent.click(child1);
rerender(<ElementsSidebar
MainContainer={MainContainer}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={SelectedContainer}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={selectContainer}
/>);
expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy();
expect((propertyParentId as HTMLInputElement).value === '').toBeFalsy();
expect((propertyId as HTMLInputElement).value).toBe(child1Model.properties.id.toString());
expect((propertyParentId as HTMLInputElement).value).toBe(child1Model.properties.parentId?.toString());
});
});

View file

@ -0,0 +1,82 @@
import { fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react';
import { describe, it, vi } from 'vitest';
import { Properties } from './Properties';
describe.concurrent('Properties', () => {
it('No properties', () => {
render(<Properties
properties={undefined}
onChange={() => {}}
/>);
expect(screen.queryByText('id')).toBeNull();
expect(screen.queryByText('parentId')).toBeNull();
expect(screen.queryByText('x')).toBeNull();
expect(screen.queryByText('y')).toBeNull();
});
it('Some properties', () => {
const prop = {
id: 'stuff',
parentId: 'parentId',
x: 1,
y: 1
};
const handleChange = vi.fn((key, value) => {
(prop as any)[key] = value;
});
const { container, rerender } = render(<Properties
properties={prop}
onChange={handleChange}
/>);
expect(screen.queryByText('id')).toBeDefined();
expect(screen.queryByText('parentId')).toBeDefined();
expect(screen.queryByText('x')).toBeDefined();
expect(screen.queryByText('y')).toBeDefined();
let propertyId = container.querySelector('#property-id');
let propertyParentId = container.querySelector('#property-parentId');
let propertyX = container.querySelector('#property-x');
let propertyY = container.querySelector('#property-y');
expect(propertyId).toBeDefined();
expect((propertyId as HTMLInputElement).value).toBe('stuff');
expect(propertyParentId).toBeDefined();
expect((propertyParentId as HTMLInputElement).value).toBe('parentId');
expect(propertyX).toBeDefined();
expect((propertyX as HTMLInputElement).value).toBe('1');
expect(propertyY).toBeDefined();
expect((propertyY as HTMLInputElement).value).toBe('1');
fireEvent.change(propertyId as Element, { target: { value: 'stuffed' } });
fireEvent.change(propertyParentId as Element, { target: { value: 'parentedId' } });
fireEvent.change(propertyX as Element, { target: { value: '2' } });
fireEvent.change(propertyY as Element, { target: { value: '2' } });
expect(handleChange).toBeCalledTimes(4);
expect(prop.id).toBe('stuffed');
expect(prop.parentId).toBe('parentedId');
expect(prop.x).toBe('2');
expect(prop.y).toBe('2');
rerender(<Properties
properties={Object.assign({}, prop)}
onChange={handleChange}
/>);
propertyId = container.querySelector('#property-id');
propertyParentId = container.querySelector('#property-parentId');
propertyX = container.querySelector('#property-x');
propertyY = container.querySelector('#property-y');
expect(propertyId).toBeDefined();
expect((propertyId as HTMLInputElement).value).toBe('stuffed');
expect(propertyParentId).toBeDefined();
expect((propertyParentId as HTMLInputElement).value).toBe('parentedId');
expect(propertyX).toBeDefined();
expect((propertyX as HTMLInputElement).value).toBe('2');
expect(propertyY).toBeDefined();
expect((propertyY as HTMLInputElement).value).toBe('2');
});
});

View file

@ -1,19 +1,61 @@
import * as React from 'react';
import { describe, test, expect } from 'vitest';
import { render, screen } from '../../utils/test-utils';
import { describe, test, expect, vi } from 'vitest';
import { findByText, fireEvent, render, screen } from '../../utils/test-utils';
import Sidebar from './Sidebar';
describe('Sidebar test', () => {
test('Start empty', () => {
describe.concurrent('Sidebar', () => {
it('Start default', () => {
const handleClick = vi.fn();
render(
<Sidebar
componentOptions={[]}
isOpen={true}
onClick={() => {}}
onClick={handleClick}
buttonOnClick={() => {}}
/>
);
const stuff = screen.queryByText(/stuff/i);
const close = screen.getByText(/close/i);
expect(screen.getByText(/Components/i)).toBeDefined();
expect(screen.getByText(/Components/i).classList.contains('left-0')).toBeDefined();
expect(stuff).toBeNull();
fireEvent.click(close);
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('Start close', () => {
render(<Sidebar
componentOptions={[]}
isOpen={false}
onClick={() => {}}
buttonOnClick={() => {}}
/>);
const stuff = screen.queryByText(/stuff/i);
expect(screen.getByText(/Components/i).classList.contains('-left-64')).toBeDefined();
expect(stuff).toBeNull();
});
it('With stuff', () => {
const Type = 'stuff';
const handleButtonClick = vi.fn();
render(<Sidebar
componentOptions={[
{
Type,
Width: 30,
Height: 30,
Style: {}
}
]}
isOpen={true}
onClick={() => {}}
buttonOnClick={handleButtonClick}
/>);
const stuff = screen.getByText(/stuff/i);
expect(stuff).toBeDefined();
fireEvent.click(stuff);
expect(handleButtonClick).toHaveBeenCalledTimes(1);
});
});

View file

@ -117,7 +117,7 @@ export class UI extends React.PureComponent<IUIProps, IUIState> {
&#9776; History
</button>
<FloatingButton className={`fixed flex flex-col gap-2 items-center bottom-40 ${buttonRightOffsetClasses}`}>
<FloatingButton className={`fixed z-10 flex flex-col gap-2 items-center bottom-40 ${buttonRightOffsetClasses}`}>
<button
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
title='Export as JSON'

View file

@ -1,13 +1,13 @@
import { describe, test, expect } from 'vitest';
import { describe, it, expect } from 'vitest';
import { fetchConfiguration } from '../App';
describe('API test', () => {
test('Load environment', () => {
describe.concurrent('API test', () => {
it('Load environment', () => {
const url = import.meta.env.VITE_API_URL;
expect(url).toBe('http://localhost:5000');
});
test('Fetch configuration', async() => {
it('Fetch configuration', async() => {
const configuration = await fetchConfiguration();
expect(configuration.MainContainer).toBeDefined();
expect(configuration.MainContainer.Height).toBeGreaterThan(0);