diff --git a/README.md b/README.md index 7fa3b5c..ef6d1ce 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/App.tsx b/src/App.tsx index 705e40d..263827b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -100,8 +100,39 @@ export class App extends React.Component { 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 { 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' + } + } +} diff --git a/src/Components/ElementsSidebar/ElementsSidebar.test.tsx b/src/Components/ElementsSidebar/ElementsSidebar.test.tsx new file mode 100644 index 0000000..d52fe97 --- /dev/null +++ b/src/Components/ElementsSidebar/ElementsSidebar.test.tsx @@ -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( {}} + onPropertyChange={() => {}} + selectContainer={() => {}} + />); + + expect(screen.getByText(/Elements/i)); + expect(screen.queryByText('id')).toBeNull(); + expect(screen.queryByText(/main/i)).toBeNull(); + }); + + it('With a MainContainer', () => { + render( {}} + 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( {}} + 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( {}} + 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( {}} + 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( {}} + 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()); + }); +}); diff --git a/src/Components/Properties/Properties.test.tsx b/src/Components/Properties/Properties.test.tsx new file mode 100644 index 0000000..1ce9d25 --- /dev/null +++ b/src/Components/Properties/Properties.test.tsx @@ -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( {}} + />); + + 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(); + + 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(); + + 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'); + }); +}); diff --git a/src/Components/Sidebar/Sidebar.test.tsx b/src/Components/Sidebar/Sidebar.test.tsx index c17f48e..8f283eb 100644 --- a/src/Components/Sidebar/Sidebar.test.tsx +++ b/src/Components/Sidebar/Sidebar.test.tsx @@ -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( {}} + 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( {}} + 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( {}} + buttonOnClick={handleButtonClick} + />); + const stuff = screen.getByText(/stuff/i); + + expect(stuff).toBeDefined(); + fireEvent.click(stuff); + expect(handleButtonClick).toHaveBeenCalledTimes(1); }); }); diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index 6476af5..7748c67 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -117,7 +117,7 @@ export class UI extends React.PureComponent { ☰ History - +