test: update unit test for v1.x

This commit is contained in:
chenshenhai 2026-03-28 20:51:44 +08:00
parent 3ce0043d07
commit 49ed490020
38 changed files with 2157 additions and 3540 deletions

View file

@ -21,8 +21,8 @@ jobs:
- run: npm run test
- run: npm run build
- run: npm run version:reset-for-release
# - run: npm publish --provenance --access public -w ./packages/types --tag next
- run: npm publish --provenance --access public -w ./packages/types
- run: npm publish --provenance --access public -w ./packages/types --tag next
# - run: npm publish --provenance --access public -w ./packages/types
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npm publish --provenance --access public -w ./packages/util

File diff suppressed because one or more lines are too long

View file

@ -1,171 +0,0 @@
import { iDraw, useHistory, deepClone, createElement, findElementFromListByPosition } from 'idraw';
const createData = () => ({
elements: [
createElement('rect', {
uuid: 'test-001',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
background: '#DDDDDD'
}
}),
createElement('group', {
uuid: 'test-005',
detail: {
children: [
createElement('image', { uuid: 'test-004', detail: { src: 'https://example.com/001.png' } }),
createElement('circle', { uuid: 'test-007' }),
createElement('text', {
uuid: 'test-008',
detail: {
text: 'Text in Group'
}
}),
createElement('image', { uuid: 'test-009', detail: { src: 'https://example.com/002.png' } })
]
}
})
]
});
describe('idraw: useHistory ', () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('updateElement', () => {
const data = createData();
const div = document.createElement('div') as HTMLDivElement;
const idraw = new iDraw(div, {
height: 200,
width: 200
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
// modify 1: do
const newElement1 = idraw.createElement('rect', {
x: 22,
y: 33,
h: 300,
w: 400,
name: 'new element 001',
detail: {
background: '#666666'
}
});
const position = [1, 2];
idraw.addElement(newElement1, {
position
});
const record1 = {
type: 'addElement',
time: new Date().getTime(),
content: {
method: 'addElement',
uuid: newElement1.uuid,
position: [...position],
element: deepClone(newElement1)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(newElement1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 2: do
const newElement2 = idraw.createElement('text', {
x: 22,
y: 33,
h: 300,
w: 400,
name: 'new element 002',
detail: {
text: 'Hello Element'
}
});
idraw.addElement(newElement2, { position });
const record2 = {
type: 'addElement',
time: new Date().getTime(),
content: {
method: 'addElement',
uuid: newElement2.uuid,
position: [...position],
element: deepClone(newElement2)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(newElement2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 3: undo
undo();
const record3 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'deleteElement',
uuid: record2.content.uuid,
position: deepClone(record2.content.position),
element: deepClone(record2.content.element)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(newElement1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 4: undo
undo();
const record4 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'deleteElement',
uuid: record1.content.uuid,
position: deepClone(record1.content.position),
element: deepClone(record1.content.element)
}
};
expect(idraw.getData()).toStrictEqual(createData());
expect(__getDoRecords()).toStrictEqual([]);
expect(__getUndoRecords()).toStrictEqual([record3, record4]);
// modify 5: redo
redo();
const record5 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'addElement',
uuid: record4.content.uuid,
position: record4.content.position,
element: deepClone(record4.content.element)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(newElement1);
expect(__getDoRecords()).toStrictEqual([record5]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 5: redo
redo();
const record6 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'addElement',
uuid: record3.content.uuid,
position: record3.content.position,
element: deepClone(record3.content.element)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(newElement2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);
expect(__getUndoRecords()).toStrictEqual([]);
});
});

View file

@ -0,0 +1,161 @@
import { iDraw, useHistory, deepClone, createMaterial, findMaterialFromListByPosition } from 'idraw';
const createData = () => ({
materials: [
createMaterial('rect', {
id: 'test-001',
x: 0,
y: 0,
width: 100,
height: 100,
fill: '#DDDDDD',
}),
createMaterial('group', {
id: 'test-005',
children: [
createMaterial('image', { id: 'test-004', src: 'https://example.com/001.png' }),
createMaterial('circle', { id: 'test-007' }),
createMaterial('text', {
id: 'test-008',
text: 'Text in Group',
}),
createMaterial('image', { id: 'test-009', src: 'https://example.com/002.png' }),
],
}),
],
});
describe('idraw: useHistory ', () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('updateMaterial', () => {
const data = createData();
const div = document.createElement('div') as HTMLDivElement;
const idraw = new iDraw(div, {
height: 200,
width: 200,
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
// modify 1: do
const newMaterial1 = idraw.createMaterial('rect', {
x: 22,
y: 33,
height: 300,
width: 400,
name: 'new material 001',
fill: '#666666',
});
const position = [1, 2];
idraw.addMaterial(newMaterial1, {
position,
});
const record1 = {
type: 'addMaterial',
time: new Date().getTime(),
content: {
method: 'addMaterial',
id: newMaterial1.id,
position: [...position],
material: deepClone(newMaterial1),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(newMaterial1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 2: do
const newMaterial2 = idraw.createMaterial('text', {
x: 22,
y: 33,
height: 300,
width: 400,
name: 'new material 002',
text: 'Hello Material',
});
idraw.addMaterial(newMaterial2, { position });
const record2 = {
type: 'addMaterial',
time: new Date().getTime(),
content: {
method: 'addMaterial',
id: newMaterial2.id,
position: [...position],
material: deepClone(newMaterial2),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(newMaterial2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 3: undo
undo();
const record3 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'deleteMaterial',
id: record2.content.id,
position: deepClone(record2.content.position),
material: deepClone(record2.content.material),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(newMaterial1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 4: undo
undo();
const record4 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'deleteMaterial',
id: record1.content.id,
position: deepClone(record1.content.position),
material: deepClone(record1.content.material),
},
};
expect(idraw.getData()).toStrictEqual(createData());
expect(__getDoRecords()).toStrictEqual([]);
expect(__getUndoRecords()).toStrictEqual([record3, record4]);
// modify 5: redo
redo();
const record5 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'addMaterial',
id: record4.content.id,
position: record4.content.position,
material: deepClone(record4.content.material),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(newMaterial1);
expect(__getDoRecords()).toStrictEqual([record5]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 5: redo
redo();
const record6 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'addMaterial',
id: record3.content.id,
position: record3.content.position,
material: deepClone(record3.content.material),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(newMaterial2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);
expect(__getUndoRecords()).toStrictEqual([]);
});
});

View file

@ -1,158 +0,0 @@
import { iDraw, useHistory, deepClone, createElement, findElementFromListByPosition } from 'idraw';
import type { Element } from 'idraw';
const createData = () => ({
elements: [
createElement('rect', {
uuid: 'test-000',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
background: '#DDDDDD'
}
}),
createElement('group', {
uuid: 'test-001',
detail: {
children: [
createElement('image', { uuid: 'test-001-000', detail: { src: 'https://example.com/001.png' } }),
createElement('circle', { uuid: 'test-001-001' }),
createElement('text', {
uuid: 'test-001-002',
detail: {
text: 'Text in Group'
}
}),
createElement('image', { uuid: 'test-001-003', detail: { src: 'https://example.com/002.png' } }),
createElement('rect', { uuid: 'test-001-004' }),
createElement('circle', { uuid: 'test-001-005' })
]
}
})
]
});
describe('idraw: useHistory ', () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('updateElement', () => {
const data = createData();
const div = document.createElement('div') as HTMLDivElement;
const idraw = new iDraw(div, {
height: 200,
width: 200
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
const position = [1, 2];
const nextPosition = [1, 3];
// modify 1: do
const deletedElem1 = deepClone(findElementFromListByPosition(position, data.elements) as Element);
const expectedElem1 = deepClone(findElementFromListByPosition(nextPosition, data.elements) as Element);
idraw.deleteElement(deletedElem1?.uuid);
const record1 = {
type: 'deleteElement',
time: new Date().getTime(),
content: {
method: 'deleteElement',
uuid: deletedElem1.uuid,
position: [...position],
element: deepClone(deletedElem1)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(expectedElem1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 2: do
const deletedElem2 = deepClone(findElementFromListByPosition(position, data.elements) as Element);
const expectedElem2 = deepClone(findElementFromListByPosition(nextPosition, data.elements) as Element);
idraw.deleteElement(deletedElem2?.uuid);
const record2 = {
type: 'deleteElement',
time: new Date().getTime(),
content: {
method: 'deleteElement',
uuid: deletedElem2.uuid,
position: [...position],
element: deepClone(deletedElem2)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(expectedElem2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 3: undo
undo();
const record3 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'addElement',
uuid: record2.content.uuid,
position: deepClone(record2.content.position),
element: deepClone(record2.content.element)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(deletedElem2);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 4: undo
undo();
const record4 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'addElement',
uuid: record1.content.uuid,
position: deepClone(record1.content.position),
element: deepClone(record1.content.element)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(deletedElem1);
expect(__getDoRecords()).toStrictEqual([]);
expect(__getUndoRecords()).toStrictEqual([record3, record4]);
// modify 5: redo
redo();
const record5 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'deleteElement',
uuid: record4.content.uuid,
position: record4.content.position,
element: deepClone(record4.content.element)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(expectedElem1);
expect(__getDoRecords()).toStrictEqual([record5]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 5: redo
redo();
const record6 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'deleteElement',
uuid: record3.content.uuid,
position: record3.content.position,
element: deepClone(record3.content.element)
}
};
expect(findElementFromListByPosition(position, idraw.getData()?.elements || [])).toStrictEqual(expectedElem2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);
expect(__getUndoRecords()).toStrictEqual([]);
});
});

View file

@ -0,0 +1,152 @@
import { iDraw, useHistory, deepClone, createMaterial, findMaterialFromListByPosition } from 'idraw';
import type { Material } from 'idraw';
const createData = () => ({
materials: [
createMaterial('rect', {
id: 'test-000',
x: 0,
y: 0,
width: 100,
height: 100,
fill: '#DDDDDD',
}),
createMaterial('group', {
id: 'test-001',
children: [
createMaterial('image', { id: 'test-001-000', src: 'https://example.com/001.png' }),
createMaterial('circle', { id: 'test-001-001' }),
createMaterial('text', {
id: 'test-001-002',
text: 'Text in Group',
}),
createMaterial('image', { id: 'test-001-003', src: 'https://example.com/002.png' }),
createMaterial('rect', { id: 'test-001-004' }),
createMaterial('circle', { id: 'test-001-005' }),
],
}),
],
});
describe('idraw: useHistory ', () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('updateMaterial', () => {
const data = createData();
const div = document.createElement('div') as HTMLDivElement;
const idraw = new iDraw(div, {
height: 200,
width: 200,
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
const position = [1, 2];
const nextPosition = [1, 3];
// modify 1: do
const deletedElem1 = deepClone(findMaterialFromListByPosition(position, data.materials) as Material);
const expectedElem1 = deepClone(findMaterialFromListByPosition(nextPosition, data.materials) as Material);
idraw.deleteMaterial(deletedElem1?.id);
const record1 = {
type: 'deleteMaterial',
time: new Date().getTime(),
content: {
method: 'deleteMaterial',
id: deletedElem1.id,
position: [...position],
material: deepClone(deletedElem1),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(expectedElem1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 2: do
const deletedElem2 = deepClone(findMaterialFromListByPosition(position, data.materials) as Material);
const expectedElem2 = deepClone(findMaterialFromListByPosition(nextPosition, data.materials) as Material);
idraw.deleteMaterial(deletedElem2?.id);
const record2 = {
type: 'deleteMaterial',
time: new Date().getTime(),
content: {
method: 'deleteMaterial',
id: deletedElem2.id,
position: [...position],
material: deepClone(deletedElem2),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(expectedElem2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 3: undo
undo();
const record3 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'addMaterial',
id: record2.content.id,
position: deepClone(record2.content.position),
material: deepClone(record2.content.material),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(deletedElem2);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 4: undo
undo();
const record4 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'addMaterial',
id: record1.content.id,
position: deepClone(record1.content.position),
material: deepClone(record1.content.material),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(deletedElem1);
expect(__getDoRecords()).toStrictEqual([]);
expect(__getUndoRecords()).toStrictEqual([record3, record4]);
// modify 5: redo
redo();
const record5 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'deleteMaterial',
id: record4.content.id,
position: record4.content.position,
material: deepClone(record4.content.material),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(expectedElem1);
expect(__getDoRecords()).toStrictEqual([record5]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 5: redo
redo();
const record6 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'deleteMaterial',
id: record3.content.id,
position: record3.content.position,
material: deepClone(record3.content.material),
},
};
expect(findMaterialFromListByPosition(position, idraw.getData()?.materials || [])).toStrictEqual(expectedElem2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);
expect(__getUndoRecords()).toStrictEqual([]);
});
});

View file

@ -1,44 +1,36 @@
import { iDraw, useHistory, deepClone, createElement, set, get, toFlattenGlobal } from 'idraw';
import { iDraw, useHistory, deepClone, createMaterial, set, get, toFlattenGlobal } from 'idraw';
import type { Data, DataGlobal, RecursivePartial } from 'idraw';
const createData = () =>
({
elements: [
createElement('rect', {
uuid: 'test-001',
materials: [
createMaterial('rect', {
id: 'test-001',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
background: '#DDDDDD'
}
width: 100,
height: 100,
fill: '#DDDDDD',
}),
createElement('circle', { uuid: 'test-002' }),
createElement('text', {
uuid: 'test-003',
detail: {
text: 'Hello World'
}
createMaterial('circle', { id: 'test-002' }),
createMaterial('text', {
id: 'test-003',
text: 'Hello World',
}),
createElement('image', { uuid: 'test-004', detail: { src: 'https://example.com/001.png' } }),
createElement('group', {
uuid: 'test-005',
detail: {
children: [
createElement('rect', { uuid: 'test-006' }),
createElement('circle', { uuid: 'test-007' }),
createElement('text', {
uuid: 'test-008',
detail: {
text: 'Text in Group'
}
}),
createElement('image', { uuid: 'test-009', detail: { src: 'https://example.com/002.png' } })
]
}
})
]
createMaterial('image', { id: 'test-004', src: 'https://example.com/001.png' }),
createMaterial('group', {
id: 'test-005',
children: [
createMaterial('rect', { id: 'test-006' }),
createMaterial('circle', { id: 'test-007' }),
createMaterial('text', {
id: 'test-008',
text: 'Text in Group',
}),
createMaterial('image', { id: 'test-009', src: 'https://example.com/002.png' }),
],
}),
],
} as Data);
describe('idraw: useHistory ', () => {
@ -52,7 +44,7 @@ describe('idraw: useHistory ', () => {
const idraw = new iDraw(div, {
height: 200,
width: 200
width: 200,
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
@ -61,14 +53,14 @@ describe('idraw: useHistory ', () => {
// modify 1: do
const modifiedInfo1 = {
background: '#123456'
fill: '#123456',
};
idraw.modifyGlobal({
...deepClone(modifiedInfo1)
...deepClone(modifiedInfo1),
});
const expectedData1 = createData();
const flattenModifiedInfo1 = toFlattenGlobal(modifiedInfo1);
const beforeInfo1: Record<string, any> | null = null;
const beforeInfo1: Record<string, unknown> | null = null;
const afterInfo1 = { ...flattenModifiedInfo1 };
Object.keys(flattenModifiedInfo1).forEach((k) => {
@ -81,8 +73,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyGlobal',
before: beforeInfo1,
after: afterInfo1
}
after: afterInfo1,
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
@ -90,14 +82,14 @@ describe('idraw: useHistory ', () => {
// modify 2: do
const modifiedInfo2 = {
background: '#AAAAAA'
fill: '#AAAAAA',
} as unknown as RecursivePartial<DataGlobal>;
idraw.modifyGlobal({ ...modifiedInfo2 });
const expectedData2 = deepClone(expectedData1);
const flattenModifiedInfo2 = toFlattenGlobal(modifiedInfo2);
const beforeInfo2: Record<string, any> = {};
const beforeInfo2: Record<string, unknown> = {};
const afterInfo2 = { ...flattenModifiedInfo2 };
Object.keys(flattenModifiedInfo2).forEach((key) => {
@ -110,8 +102,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyGlobal',
before: beforeInfo2,
after: afterInfo2
}
after: afterInfo2,
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
@ -125,8 +117,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyGlobal',
before: deepClone(record2.content.after),
after: deepClone(record2.content.before)
}
after: deepClone(record2.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
@ -140,8 +132,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyGlobal',
before: deepClone(record1.content.after),
after: deepClone(record1.content.before)
}
after: deepClone(record1.content.before),
},
};
expect(idraw.getData()).toStrictEqual(createData());
expect(__getDoRecords()).toStrictEqual([]);
@ -155,8 +147,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyGlobal',
before: deepClone(record4.content.after),
after: deepClone(record4.content.before)
}
after: deepClone(record4.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record5]);
@ -170,8 +162,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyGlobal',
before: deepClone(record3.content.after),
after: deepClone(record3.content.before)
}
after: deepClone(record3.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);

View file

@ -1,44 +1,36 @@
import { iDraw, useHistory, deepClone, createElement, set, get, toFlattenLayout } from 'idraw';
import { iDraw, useHistory, deepClone, createMaterial, set, get, toFlattenLayout } from 'idraw';
import type { Data, DataLayout, RecursivePartial } from 'idraw';
const createData = () =>
({
elements: [
createElement('rect', {
uuid: 'test-001',
materials: [
createMaterial('rect', {
id: 'test-001',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
background: '#DDDDDD'
}
width: 100,
height: 100,
fill: '#DDDDDD',
}),
createElement('circle', { uuid: 'test-002' }),
createElement('text', {
uuid: 'test-003',
detail: {
text: 'Hello World'
}
createMaterial('circle', { id: 'test-002' }),
createMaterial('text', {
id: 'test-003',
text: 'Hello World',
}),
createElement('image', { uuid: 'test-004', detail: { src: 'https://example.com/001.png' } }),
createElement('group', {
uuid: 'test-005',
detail: {
children: [
createElement('rect', { uuid: 'test-006' }),
createElement('circle', { uuid: 'test-007' }),
createElement('text', {
uuid: 'test-008',
detail: {
text: 'Text in Group'
}
}),
createElement('image', { uuid: 'test-009', detail: { src: 'https://example.com/002.png' } })
]
}
})
]
createMaterial('image', { id: 'test-004', src: 'https://example.com/001.png' }),
createMaterial('group', {
id: 'test-005',
children: [
createMaterial('rect', { id: 'test-006' }),
createMaterial('circle', { id: 'test-007' }),
createMaterial('text', {
id: 'test-008',
text: 'Text in Group',
}),
createMaterial('image', { id: 'test-009', src: 'https://example.com/002.png' }),
],
}),
],
} as Data);
describe('idraw: useHistory ', () => {
@ -52,7 +44,7 @@ describe('idraw: useHistory ', () => {
const idraw = new iDraw(div, {
height: 200,
width: 200
width: 200,
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
@ -63,19 +55,17 @@ describe('idraw: useHistory ', () => {
const modifiedInfo1 = {
x: 1,
y: 2,
w: 100,
h: 200,
detail: {
background: '#123456',
borderRadius: 3
}
width: 100,
height: 200,
fill: '#123456',
cornerRadius: 3,
};
idraw.modifyLayout({
...deepClone(modifiedInfo1)
...deepClone(modifiedInfo1),
});
const expectedData1 = createData();
const flattenModifiedInfo1 = toFlattenLayout(modifiedInfo1);
const beforeInfo1: Record<string, any> | null = null;
const beforeInfo1: Record<string, unknown> | null = null;
const afterInfo1 = { ...flattenModifiedInfo1 };
Object.keys(flattenModifiedInfo1).forEach((k) => {
@ -88,8 +78,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyLayout',
before: beforeInfo1,
after: afterInfo1
}
after: afterInfo1,
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
@ -99,22 +89,20 @@ describe('idraw: useHistory ', () => {
const modifiedInfo2 = {
x: modifiedInfo1.x + 3,
y: modifiedInfo1.y + 4,
detail: {
borderRadius: [2, 4, 6, 8]
}
cornerRadius: [2, 4, 6, 8],
} as unknown as RecursivePartial<DataLayout>;
idraw.modifyLayout({ ...modifiedInfo2 });
const expectedData2 = deepClone(expectedData1);
const flattenModifiedInfo2 = toFlattenLayout(modifiedInfo2);
const beforeInfo2: Record<string, any> = {};
const beforeInfo2: Record<string, unknown> = {};
const afterInfo2 = { ...flattenModifiedInfo2 };
Object.keys(flattenModifiedInfo2).forEach((key) => {
let beforeVal = get(expectedData1.layout, key);
let beforeKey = key;
if (beforeVal === undefined && /(borderRadius|borderWidth)\[[0-9]{1,}\]$/.test(beforeKey)) {
if (beforeVal === undefined && /(cornerRadius|strokeWidth)\[[0-9]{1,}\]$/.test(beforeKey)) {
beforeKey = beforeKey.replace(/\[[0-9]{1,}\]$/, '');
beforeVal = get(expectedData1.layout, beforeKey);
}
@ -127,8 +115,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyLayout',
before: beforeInfo2,
after: afterInfo2
}
after: afterInfo2,
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
@ -142,8 +130,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyLayout',
before: deepClone(record2.content.after),
after: deepClone(record2.content.before)
}
after: deepClone(record2.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
@ -157,8 +145,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyLayout',
before: deepClone(record1.content.after),
after: deepClone(record1.content.before)
}
after: deepClone(record1.content.before),
},
};
expect(idraw.getData()).toStrictEqual(createData());
expect(__getDoRecords()).toStrictEqual([]);
@ -172,8 +160,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyLayout',
before: deepClone(record4.content.after),
after: deepClone(record4.content.before)
}
after: deepClone(record4.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record5]);
@ -187,8 +175,8 @@ describe('idraw: useHistory ', () => {
content: {
method: 'modifyLayout',
before: deepClone(record3.content.after),
after: deepClone(record3.content.before)
}
after: deepClone(record3.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);

View file

@ -1,43 +1,35 @@
import { iDraw, useHistory, deepClone, createElement, set, get, toFlattenElement } from 'idraw';
import type { RecursivePartial, Element } from 'idraw';
import { iDraw, useHistory, deepClone, createMaterial, set, get, toFlattenMaterial } from 'idraw';
import type { RecursivePartial, Material } from 'idraw';
const createData = () => ({
elements: [
createElement('rect', {
uuid: 'test-001',
materials: [
createMaterial('rect', {
id: 'test-001',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
background: '#DDDDDD'
}
width: 100,
height: 100,
fill: '#DDDDDD',
}),
createElement('circle', { uuid: 'test-002' }),
createElement('text', {
uuid: 'test-003',
detail: {
text: 'Hello World'
}
createMaterial('circle', { id: 'test-002' }),
createMaterial('text', {
id: 'test-003',
text: 'Hello World',
}),
createElement('image', { uuid: 'test-004', detail: { src: 'https://example.com/001.png' } }),
createElement('group', {
uuid: 'test-005',
detail: {
children: [
createElement('rect', { uuid: 'test-006' }),
createElement('circle', { uuid: 'test-007' }),
createElement('text', {
uuid: 'test-008',
detail: {
text: 'Text in Group'
}
}),
createElement('image', { uuid: 'test-009', detail: { src: 'https://example.com/002.png' } })
]
}
})
]
createMaterial('image', { id: 'test-004', src: 'https://example.com/001.png' }),
createMaterial('group', {
id: 'test-005',
children: [
createMaterial('rect', { id: 'test-006' }),
createMaterial('circle', { id: 'test-007' }),
createMaterial('text', {
id: 'test-008',
text: 'Text in Group',
}),
createMaterial('image', { id: 'test-009', src: 'https://example.com/002.png' }),
],
}),
],
});
describe('idraw: useHistory ', () => {
@ -45,50 +37,48 @@ describe('idraw: useHistory ', () => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('modifyElement', () => {
test('modifyMaterial', () => {
const data = createData();
const div = document.createElement('div') as HTMLDivElement;
const idraw = new iDraw(div, {
height: 200,
width: 200
width: 200,
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
const targetElement = deepClone(data.elements[0]);
const targetMaterial = deepClone(data.materials[0]);
// modify 1: do
const modifiedInfo1 = {
x: targetElement.x + 1,
y: targetElement.y + 2,
detail: {
background: '#123456',
borderRadius: 3
}
x: targetMaterial.x + 1,
y: targetMaterial.y + 2,
fill: '#123456',
cornerRadius: 3,
};
idraw.modifyElement({
uuid: targetElement.uuid,
...deepClone(modifiedInfo1)
idraw.modifyMaterial({
id: targetMaterial.id,
...deepClone(modifiedInfo1),
});
const expectedData1 = createData();
const flattenModifiedInfo1 = toFlattenElement(modifiedInfo1);
const beforeInfo1: Record<string, any> = {};
const flattenModifiedInfo1 = toFlattenMaterial(modifiedInfo1);
const beforeInfo1: Record<string, unknown> = {};
const afterInfo1 = { ...flattenModifiedInfo1 };
Object.keys(flattenModifiedInfo1).forEach((key) => {
beforeInfo1[key] = get(expectedData1.elements[0], key);
set(expectedData1.elements[0], key, flattenModifiedInfo1[key]);
beforeInfo1[key] = get(expectedData1.materials[0], key);
set(expectedData1.materials[0], key, flattenModifiedInfo1[key]);
});
const record1 = {
type: 'modifyElement',
type: 'modifyMaterial',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: beforeInfo1,
after: afterInfo1
}
after: afterInfo1,
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
@ -98,40 +88,38 @@ describe('idraw: useHistory ', () => {
const modifiedInfo2 = {
x: modifiedInfo1.x + 3,
y: modifiedInfo1.y + 4,
detail: {
borderRadius: [2, 4, 6, 8]
}
} as unknown as RecursivePartial<Omit<Element, 'uuid'>>;
cornerRadius: [2, 4, 6, 8],
} as unknown as RecursivePartial<Omit<Material, 'id'>>;
idraw.modifyElement({
uuid: targetElement.uuid,
...deepClone(modifiedInfo2)
} as RecursivePartial<Omit<Element, 'uuid'>> & Pick<Element, 'uuid'>);
idraw.modifyMaterial({
id: targetMaterial.id,
...deepClone(modifiedInfo2),
} as RecursivePartial<Omit<Material, 'id'>> & Pick<Material, 'id'>);
const expectedData2 = deepClone(expectedData1);
const flattenModifiedInfo2 = toFlattenElement(modifiedInfo2);
const beforeInfo2: Record<string, any> = {};
const flattenModifiedInfo2 = toFlattenMaterial(modifiedInfo2);
const beforeInfo2: Record<string, unknown> = {};
const afterInfo2 = { ...flattenModifiedInfo2 };
Object.keys(flattenModifiedInfo2).forEach((key) => {
let beforeVal = get(expectedData1.elements[0], key);
let beforeVal = get(expectedData1.materials[0], key);
let beforeKey = key;
if (beforeVal === undefined && /(borderRadius|borderWidth)\[[0-9]{1,}\]$/.test(beforeKey)) {
if (beforeVal === undefined && /(cornerRadius|strokeWidth)\[[0-9]{1,}\]$/.test(beforeKey)) {
beforeKey = beforeKey.replace(/\[[0-9]{1,}\]$/, '');
beforeVal = get(expectedData1.elements[0], beforeKey);
beforeVal = get(expectedData1.materials[0], beforeKey);
}
beforeInfo2[beforeKey] = beforeVal;
set(expectedData2.elements[0], key, flattenModifiedInfo2[key]);
set(expectedData2.materials[0], key, flattenModifiedInfo2[key]);
});
const record2 = {
type: 'modifyElement',
type: 'modifyMaterial',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: beforeInfo2,
after: afterInfo2
}
after: afterInfo2,
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
@ -143,11 +131,11 @@ describe('idraw: useHistory ', () => {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record2.content.after),
after: deepClone(record2.content.before)
}
after: deepClone(record2.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
@ -159,11 +147,11 @@ describe('idraw: useHistory ', () => {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record1.content.after),
after: deepClone(record1.content.before)
}
after: deepClone(record1.content.before),
},
};
expect(idraw.getData()).toStrictEqual(createData());
expect(__getDoRecords()).toStrictEqual([]);
@ -175,11 +163,11 @@ describe('idraw: useHistory ', () => {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record4.content.after),
after: deepClone(record4.content.before)
}
after: deepClone(record4.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record5]);
@ -191,11 +179,11 @@ describe('idraw: useHistory ', () => {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record3.content.after),
after: deepClone(record3.content.before)
}
after: deepClone(record3.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);

View file

@ -0,0 +1,222 @@
import { iDraw, useHistory, deepClone, createMaterial, set, get, toFlattenMaterial } from 'idraw';
import type { RecursivePartial, Material } from 'idraw';
const createData = () => ({
materials: [
createMaterial('group', {
id: 'test-001',
x: 0,
y: 0,
width: 2000,
height: 2000,
children: [
createMaterial('rect', { id: 'test-002', x: 20, y: 20, width: 20, height: 20 }),
createMaterial('circle', { id: 'test-003', x: 40, y: 40, width: 40, height: 40 }),
createMaterial('text', {
id: 'test-004',
x: 60,
y: 60,
width: 60,
height: 60,
fontSize: 16,
text: 'Text in Group',
}),
createMaterial('image', {
id: 'test-005',
x: 80,
y: 80,
width: 80,
height: 80,
src: 'https://example.com/002.png',
}),
createMaterial('group', {
id: 'test-101',
x: 500,
y: 500,
width: 1000,
height: 1000,
children: [
createMaterial('rect', { id: 'test-102', x: 20, y: 20, width: 20, height: 20 }),
createMaterial('circle', { id: 'test-103', x: 40, y: 40, width: 40, height: 40 }),
createMaterial('text', {
id: 'test-104',
x: 60,
y: 60,
width: 60,
height: 60,
fontSize: 16,
text: 'Text in Group',
}),
createMaterial('image', {
id: 'test-105',
x: 80,
y: 80,
width: 80,
height: 80,
src: 'https://example.com/002.png',
}),
],
}),
],
}),
],
});
describe('idraw: useHistory ', () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('modifyMaterial', () => {
const data = createData();
const div = document.createElement('div') as HTMLDivElement;
const idraw = new iDraw(div, {
height: 200,
width: 200,
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
const targetMaterial = deepClone(data.materials[0]);
// modify 1: do
const modifiedInfo1 = {
x: targetMaterial.x + 1,
y: targetMaterial.y + 2,
fill: '#123456',
cornerRadius: 3,
};
idraw.modifyMaterial({
id: targetMaterial.id,
...deepClone(modifiedInfo1),
});
const expectedData1 = createData();
const flattenModifiedInfo1 = toFlattenMaterial(modifiedInfo1);
const beforeInfo1: Record<string, unknown> = {};
const afterInfo1 = { ...flattenModifiedInfo1 };
Object.keys(flattenModifiedInfo1).forEach((key) => {
beforeInfo1[key] = get(expectedData1.materials[0], key);
set(expectedData1.materials[0], key, flattenModifiedInfo1[key]);
});
const record1 = {
type: 'modifyMaterial',
time: new Date().getTime(),
content: {
method: 'modifyMaterial',
id: targetMaterial.id,
before: beforeInfo1,
after: afterInfo1,
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 2: do
const modifiedInfo2 = {
x: modifiedInfo1.x + 3,
y: modifiedInfo1.y + 4,
cornerRadius: [2, 4, 6, 8],
} as unknown as RecursivePartial<Omit<Material, 'id'>>;
idraw.modifyMaterial({
id: targetMaterial.id,
...deepClone(modifiedInfo2),
} as RecursivePartial<Omit<Material, 'id'>> & Pick<Material, 'id'>);
const expectedData2 = deepClone(expectedData1);
const flattenModifiedInfo2 = toFlattenMaterial(modifiedInfo2);
const beforeInfo2: Record<string, unknown> = {};
const afterInfo2 = { ...flattenModifiedInfo2 };
Object.keys(flattenModifiedInfo2).forEach((key) => {
let beforeVal = get(expectedData1.materials[0], key);
let beforeKey = key;
if (beforeVal === undefined && /(cornerRadius|strokeWidth)\[[0-9]{1,}\]$/.test(beforeKey)) {
beforeKey = beforeKey.replace(/\[[0-9]{1,}\]$/, '');
beforeVal = get(expectedData1.materials[0], beforeKey);
}
beforeInfo2[beforeKey] = beforeVal;
set(expectedData2.materials[0], key, flattenModifiedInfo2[key]);
});
const record2 = {
type: 'modifyMaterial',
time: new Date().getTime(),
content: {
method: 'modifyMaterial',
id: targetMaterial.id,
before: beforeInfo2,
after: afterInfo2,
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 3: undo
undo();
const record3 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record2.content.after),
after: deepClone(record2.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 4: undo
undo();
const record4 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record1.content.after),
after: deepClone(record1.content.before),
},
};
expect(idraw.getData()).toStrictEqual(createData());
expect(__getDoRecords()).toStrictEqual([]);
expect(__getUndoRecords()).toStrictEqual([record3, record4]);
// modify 5: redo
redo();
const record5 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record4.content.after),
after: deepClone(record4.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record5]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 5: redo
redo();
const record6 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record3.content.after),
after: deepClone(record3.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);
expect(__getUndoRecords()).toStrictEqual([]);
});
});

View file

@ -1,42 +1,39 @@
import { iDraw, useHistory, findElementFromListByPosition, calcResultMovePosition } from 'idraw';
import type { Elements } from 'idraw';
import { iDraw, useHistory, findMaterialFromListByPosition, calcResultMovePosition } from 'idraw';
import type { StrictMaterial } from 'idraw';
const getElemBase = () => {
return {
x: 0,
y: 0,
w: 1,
h: 1
width: 1,
height: 1,
};
};
function generateElements(list: any[]): Elements {
const elements: Elements = list.map((item) => {
function generateMaterials(list: any[]): StrictMaterial[] {
const materials: StrictMaterial[] = list.map((item) => {
if (Array.isArray(item)) {
const groupIds = item[0].split('-');
groupIds.pop();
return {
...getElemBase(),
uuid: groupIds.join('-'),
id: groupIds.join('-'),
type: 'group',
detail: {
children: generateElements(item)
}
children: generateMaterials(item),
};
} else {
return {
...getElemBase(),
uuid: item,
id: item,
type: 'rect',
detail: {}
};
}
}) as Elements;
return elements;
}) as StrictMaterial[];
return materials;
}
const createData = (list: any[]) => ({
elements: generateElements(list)
materials: generateMaterials(list),
});
describe('idraw: useHistory ', () => {
@ -44,14 +41,14 @@ describe('idraw: useHistory ', () => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('moveElement', () => {
test('moveMaterial', () => {
const getList1 = () => ['0', '1', '2', ['3-0', '3-1', ['3-2-0', '3-2-1', '3-2-2', '3-2-3'], '3-3'], '4', '5'];
const data = createData(getList1());
const div = document.createElement('div') as HTMLDivElement;
const idraw = new iDraw(div, {
height: 200,
width: 200
width: 200,
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
@ -62,69 +59,69 @@ describe('idraw: useHistory ', () => {
const from1 = [3, 2, 1];
const to1 = [2];
// result from: [ 4, 2, 1 ], to: [ 2 ]
const uuid1 = findElementFromListByPosition(from1, data.elements)?.uuid as string;
idraw.moveElement(uuid1, to1);
const id1 = findMaterialFromListByPosition(from1, data.materials)?.id as string;
idraw.moveMaterial(id1, to1);
const record1 = {
type: 'moveElement',
type: 'moveMaterial',
time: new Date().getTime(),
content: {
method: 'moveElement',
uuid: uuid1,
method: 'moveMaterial',
id: id1,
from: [...from1],
to: [...to1]
}
to: [...to1],
},
};
// ['0', '1', '3-2-1', '2', ['3-0', '3-1', ['3-2-0', '3-2-2', '3-2-3'], '3-3'], '4', '5'];
const expectedElements1 = generateElements([
const expectedMaterials1 = generateMaterials([
'0',
'1',
'3-2-1',
'2',
['3-0', '3-1', ['3-2-0', '3-2-2', '3-2-3'], '3-3'],
'4',
'5'
'5',
]);
// const expectedElements1 = moveElementPosition(generateElements(getList1()), {
// const expectedMaterials1 = moveMaterialPosition(generateMaterials(getList1()), {
// from: [...from1],
// to: [...to1]
// }).elements;
// }).materials;
expect(idraw.getData()?.elements).toStrictEqual(expectedElements1);
expect(idraw.getData()?.materials).toStrictEqual(expectedMaterials1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 2: do
const from2 = [2];
const to2 = [4];
const uuid2 = findElementFromListByPosition(from2, data.elements)?.uuid as string;
// console.log('uuid2 ----- ', uuid2, findElementFromListByPosition(to2, data.elements)?.uuid);
idraw.moveElement(uuid1, to2);
const id2 = findMaterialFromListByPosition(from2, data.materials)?.id as string;
// console.log('id2 ----- ', id2, findMaterialFromListByPosition(to2, data.materials)?.id);
idraw.moveMaterial(id1, to2);
const record2 = {
type: 'moveElement',
type: 'moveMaterial',
time: new Date().getTime(),
content: {
method: 'moveElement',
uuid: uuid2,
method: 'moveMaterial',
id: id2,
from: [...from2],
to: [...to2]
}
to: [...to2],
},
};
const expectedElements2 = generateElements([
const expectedMaterials2 = generateMaterials([
'0',
'1',
'2',
'3-2-1',
['3-0', '3-1', ['3-2-0', '3-2-2', '3-2-3'], '3-3'],
'4',
'5'
'5',
]);
// const expectedElements2 = moveElementPosition(expectedElements1, {
// const expectedMaterials2 = moveMaterialPosition(expectedMaterials1, {
// from: [...from2],
// to: [...to2]
// }).elements;
expect(idraw.getData()?.elements).toStrictEqual(expectedElements2);
// }).materials;
expect(idraw.getData()?.materials).toStrictEqual(expectedMaterials2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
expect(__getUndoRecords()).toStrictEqual([]);
@ -132,32 +129,32 @@ describe('idraw: useHistory ', () => {
undo();
const moveResult2 = calcResultMovePosition({
from: [...from2],
to: [...to2]
to: [...to2],
}) as { from: number[]; to: number[] };
const record3 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'moveElement',
uuid: record2.content.uuid,
method: 'moveMaterial',
id: record2.content.id,
from: [...moveResult2.to],
to: [...moveResult2.from]
}
to: [...moveResult2.from],
},
};
const expectedElements3 = generateElements([
const expectedMaterials3 = generateMaterials([
'0',
'1',
'3-2-1',
'2',
['3-0', '3-1', ['3-2-0', '3-2-2', '3-2-3'], '3-3'],
'4',
'5'
'5',
]);
// const expectedElements3 = moveElementPosition(expectedElements1, {
// const expectedMaterials3 = moveMaterialPosition(expectedMaterials1, {
// from: [...moveResult2.to],
// to: [...moveResult2.from]
// }).elements;
expect(idraw.getData()?.elements).toStrictEqual(expectedElements3);
// }).materials;
expect(idraw.getData()?.materials).toStrictEqual(expectedMaterials3);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([record3]);
@ -165,31 +162,31 @@ describe('idraw: useHistory ', () => {
undo();
const moveResult3 = calcResultMovePosition({
from: [...from1],
to: [...to1]
to: [...to1],
}) as { from: number[]; to: number[] };
const record4 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'moveElement',
uuid: record1.content.uuid,
method: 'moveMaterial',
id: record1.content.id,
from: [...moveResult3.to],
to: [...moveResult3.from]
}
to: [...moveResult3.from],
},
};
const expectedElements4 = generateElements([
const expectedMaterials4 = generateMaterials([
'0',
'1',
'2',
['3-0', '3-1', ['3-2-0', '3-2-1', '3-2-2', '3-2-3'], '3-3'],
'4',
'5'
'5',
]);
// const expectedElements4 = moveElementPosition(expectedElements3, {
// const expectedMaterials4 = moveMaterialPosition(expectedMaterials3, {
// from: [...moveResult3.to],
// to: [...moveResult3.from]
// }).elements;
expect(idraw.getData()?.elements).toStrictEqual(expectedElements4);
// }).materials;
expect(idraw.getData()?.materials).toStrictEqual(expectedMaterials4);
expect(__getDoRecords()).toStrictEqual([]);
expect(__getUndoRecords()).toStrictEqual([record3, record4]);
@ -197,32 +194,32 @@ describe('idraw: useHistory ', () => {
redo();
const moveResult4 = calcResultMovePosition({
from: [...record4.content.from],
to: [...record4.content.to]
to: [...record4.content.to],
}) as { from: number[]; to: number[] };
const record5 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'moveElement',
uuid: record4.content.uuid,
method: 'moveMaterial',
id: record4.content.id,
from: [...moveResult4.to],
to: [...moveResult4.from]
}
to: [...moveResult4.from],
},
};
const expectedElements5 = generateElements([
const expectedMaterials5 = generateMaterials([
'0',
'1',
'3-2-1',
'2',
['3-0', '3-1', ['3-2-0', '3-2-2', '3-2-3'], '3-3'],
'4',
'5'
'5',
]);
// const expectedElements5 = moveElementPosition(expectedElements3, {
// const expectedMaterials5 = moveMaterialPosition(expectedMaterials3, {
// from: [...moveResult4.from],
// to: [...moveResult4.to]
// }).elements;
expect(idraw.getData()?.elements).toStrictEqual(expectedElements5);
// }).materials;
expect(idraw.getData()?.materials).toStrictEqual(expectedMaterials5);
expect(__getDoRecords()).toStrictEqual([record5]);
expect(__getUndoRecords()).toStrictEqual([record3]);
@ -230,28 +227,28 @@ describe('idraw: useHistory ', () => {
redo();
const moveResult5 = calcResultMovePosition({
from: [...record3.content.from],
to: [...record3.content.to]
to: [...record3.content.to],
}) as { from: number[]; to: number[] };
const record6 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'moveElement',
uuid: record4.content.uuid,
method: 'moveMaterial',
id: record4.content.id,
from: [...moveResult5.to],
to: [...moveResult5.from]
}
to: [...moveResult5.from],
},
};
const expectedElements6 = generateElements([
const expectedMaterials6 = generateMaterials([
'0',
'1',
'2',
'3-2-1',
['3-0', '3-1', ['3-2-0', '3-2-2', '3-2-3'], '3-3'],
'4',
'5'
'5',
]);
expect(idraw.getData()?.elements).toStrictEqual(expectedElements6);
expect(idraw.getData()?.materials).toStrictEqual(expectedMaterials6);
expect(__getDoRecords()).toStrictEqual([record5, record6]);
expect(__getUndoRecords()).toStrictEqual([]);
@ -259,28 +256,28 @@ describe('idraw: useHistory ', () => {
undo();
const moveResult6 = calcResultMovePosition({
from: [...record6.content.from],
to: [...record6.content.to]
to: [...record6.content.to],
}) as { from: number[]; to: number[] };
const record7 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'moveElement',
uuid: record6.content.uuid,
method: 'moveMaterial',
id: record6.content.id,
from: [...moveResult6.to],
to: [...moveResult6.from]
}
to: [...moveResult6.from],
},
};
const expectedElements7 = generateElements([
const expectedMaterials7 = generateMaterials([
'0',
'1',
'3-2-1',
'2',
['3-0', '3-1', ['3-2-0', '3-2-2', '3-2-3'], '3-3'],
'4',
'5'
'5',
]);
expect(idraw.getData()?.elements).toStrictEqual(expectedElements7);
expect(idraw.getData()?.materials).toStrictEqual(expectedMaterials7);
expect(__getDoRecords()).toStrictEqual([record5]);
expect(__getUndoRecords()).toStrictEqual([record7]);
@ -288,27 +285,27 @@ describe('idraw: useHistory ', () => {
undo();
const moveResult7 = calcResultMovePosition({
from: [...record5.content.from],
to: [...record5.content.to]
to: [...record5.content.to],
}) as { from: number[]; to: number[] };
const record8 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'moveElement',
uuid: record5.content.uuid,
method: 'moveMaterial',
id: record5.content.id,
from: [...moveResult7.to],
to: [...moveResult7.from]
}
to: [...moveResult7.from],
},
};
const expectedElements8 = generateElements([
const expectedMaterials8 = generateMaterials([
'0',
'1',
'2',
['3-0', '3-1', ['3-2-0', '3-2-1', '3-2-2', '3-2-3'], '3-3'],
'4',
'5'
'5',
]);
expect(idraw.getData()?.elements).toStrictEqual(expectedElements8);
expect(idraw.getData()?.materials).toStrictEqual(expectedMaterials8);
expect(__getDoRecords()).toStrictEqual([]);
expect(__getUndoRecords()).toStrictEqual([record7, record8]);
});

View file

@ -1,177 +0,0 @@
import { iDraw, useHistory, deepClone, createElement, toFlattenElement, mergeElement } from 'idraw';
const createData = () => ({
elements: [
createElement('rect', {
uuid: 'test-001',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
background: '#DDDDDD'
}
}),
createElement('circle', { uuid: 'test-002' }),
createElement('text', {
uuid: 'test-003',
detail: {
text: 'Hello World'
}
}),
createElement('image', { uuid: 'test-004', detail: { src: 'https://example.com/001.png' } }),
createElement('group', {
uuid: 'test-005',
detail: {
children: [
createElement('rect', { uuid: 'test-006' }),
createElement('circle', { uuid: 'test-007' }),
createElement('text', {
uuid: 'test-008',
detail: {
text: 'Text in Group'
}
}),
createElement('image', { uuid: 'test-009', detail: { src: 'https://example.com/002.png' } })
]
}
})
]
});
describe('idraw: useHistory ', () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('updateElement', () => {
const data = createData();
const div = document.createElement('div') as HTMLDivElement;
const idraw = new iDraw(div, {
height: 200,
width: 200
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
const targetElement = deepClone(data.elements[0]);
// modify 1: do
const updatedElement1 = deepClone(targetElement);
updatedElement1.x += 1;
updatedElement1.y += 2;
updatedElement1.detail.background = '#123456';
updatedElement1.detail.borderRadius = 3;
idraw.updateElement(updatedElement1);
const beforeInfo1: Record<string, any> = toFlattenElement(targetElement);
const afterInfo1: Record<string, any> = toFlattenElement(updatedElement1);
const expectedData1 = createData();
mergeElement(expectedData1.elements[0], updatedElement1);
const record1 = {
type: 'updateElement',
time: new Date().getTime(),
content: {
method: 'updateElement',
uuid: targetElement.uuid,
before: beforeInfo1,
after: afterInfo1
}
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 2: do
const updatedElement2 = deepClone(updatedElement1);
updatedElement2.x += 3;
updatedElement2.y += 4;
updatedElement2.detail.borderRadius = [2, 4, 6, 8];
idraw.updateElement(updatedElement2);
const beforeInfo2: Record<string, any> = toFlattenElement(updatedElement1);
const afterInfo2: Record<string, any> = toFlattenElement(updatedElement2);
const expectedData2 = createData();
mergeElement(expectedData2.elements[0], updatedElement2);
const record2 = {
type: 'updateElement',
time: new Date().getTime(),
content: {
method: 'updateElement',
uuid: targetElement.uuid,
before: beforeInfo2,
after: afterInfo2
}
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 3: undo
undo();
const record3 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'updateElement',
uuid: targetElement.uuid,
before: deepClone(record2.content.after),
after: deepClone(record2.content.before)
}
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 4: undo
undo();
const record4 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'updateElement',
uuid: targetElement.uuid,
before: deepClone(record1.content.after),
after: deepClone(record1.content.before)
}
};
expect(idraw.getData()).toStrictEqual(createData());
expect(__getDoRecords()).toStrictEqual([]);
expect(__getUndoRecords()).toStrictEqual([record3, record4]);
// modify 5: redo
redo();
const record5 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'updateElement',
uuid: targetElement.uuid,
before: deepClone(record4.content.after),
after: deepClone(record4.content.before)
}
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record5]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 5: redo
redo();
const record6 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'updateElement',
uuid: targetElement.uuid,
before: deepClone(record3.content.after),
after: deepClone(record3.content.before)
}
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);
expect(__getUndoRecords()).toStrictEqual([]);
});
});

View file

@ -0,0 +1,169 @@
import { iDraw, useHistory, deepClone, createMaterial, toFlattenMaterial, mergeMaterial } from 'idraw';
const createData = () => ({
materials: [
createMaterial('rect', {
id: 'test-001',
x: 0,
y: 0,
width: 100,
height: 100,
fill: '#DDDDDD',
}),
createMaterial('circle', { id: 'test-002' }),
createMaterial('text', {
id: 'test-003',
text: 'Hello World',
}),
createMaterial('image', { id: 'test-004', src: 'https://example.com/001.png' }),
createMaterial('group', {
id: 'test-005',
children: [
createMaterial('rect', { id: 'test-006' }),
createMaterial('circle', { id: 'test-007' }),
createMaterial('text', {
id: 'test-008',
text: 'Text in Group',
}),
createMaterial('image', { id: 'test-009', src: 'https://example.com/002.png' }),
],
}),
],
});
describe('idraw: useHistory ', () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('updateMaterial', () => {
const data = createData();
const div = document.createElement('div') as HTMLDivElement;
const idraw = new iDraw(div, {
height: 200,
width: 200,
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
const targetMaterial = deepClone(data.materials[0]);
// modify 1: do
const updatedMaterial1 = deepClone(targetMaterial);
updatedMaterial1.x += 1;
updatedMaterial1.y += 2;
updatedMaterial1.fill = '#123456';
updatedMaterial1.cornerRadius = 3;
idraw.updateMaterial(updatedMaterial1);
const beforeInfo1: Record<string, any> = toFlattenMaterial(targetMaterial);
const afterInfo1: Record<string, any> = toFlattenMaterial(updatedMaterial1);
const expectedData1 = createData();
mergeMaterial(expectedData1.materials[0], updatedMaterial1);
const record1 = {
type: 'updateMaterial',
time: new Date().getTime(),
content: {
method: 'updateMaterial',
id: targetMaterial.id,
before: beforeInfo1,
after: afterInfo1,
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 2: do
const updatedMaterial2 = deepClone(updatedMaterial1);
updatedMaterial2.x += 3;
updatedMaterial2.y += 4;
updatedMaterial2.cornerRadius = [2, 4, 6, 8];
idraw.updateMaterial(updatedMaterial2);
const beforeInfo2: Record<string, any> = toFlattenMaterial(updatedMaterial1);
const afterInfo2: Record<string, any> = toFlattenMaterial(updatedMaterial2);
const expectedData2 = createData();
mergeMaterial(expectedData2.materials[0], updatedMaterial2);
const record2 = {
type: 'updateMaterial',
time: new Date().getTime(),
content: {
method: 'updateMaterial',
id: targetMaterial.id,
before: beforeInfo2,
after: afterInfo2,
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
expect(__getUndoRecords()).toStrictEqual([]);
// modify 3: undo
undo();
const record3 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'updateMaterial',
id: targetMaterial.id,
before: deepClone(record2.content.after),
after: deepClone(record2.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 4: undo
undo();
const record4 = {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'updateMaterial',
id: targetMaterial.id,
before: deepClone(record1.content.after),
after: deepClone(record1.content.before),
},
};
expect(idraw.getData()).toStrictEqual(createData());
expect(__getDoRecords()).toStrictEqual([]);
expect(__getUndoRecords()).toStrictEqual([record3, record4]);
// modify 5: redo
redo();
const record5 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'updateMaterial',
id: targetMaterial.id,
before: deepClone(record4.content.after),
after: deepClone(record4.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record5]);
expect(__getUndoRecords()).toStrictEqual([record3]);
// modify 5: redo
redo();
const record6 = {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'updateMaterial',
id: targetMaterial.id,
before: deepClone(record3.content.after),
after: deepClone(record3.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);
expect(__getUndoRecords()).toStrictEqual([]);
});
});

View file

@ -1,43 +1,35 @@
import { iDraw, useHistory, deepClone, createElement, set, get, toFlattenElement } from 'idraw';
import type { RecursivePartial, Element } from 'idraw';
import { iDraw, useHistory, deepClone, createMaterial, set, get, toFlattenMaterial } from 'idraw';
import type { RecursivePartial, Material } from 'idraw';
const createData = () => ({
elements: [
createElement('rect', {
uuid: 'test-001',
materials: [
createMaterial('rect', {
id: 'test-001',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
background: '#DDDDDD'
}
width: 100,
height: 100,
fill: '#DDDDDD',
}),
createElement('circle', { uuid: 'test-002' }),
createElement('text', {
uuid: 'test-003',
detail: {
text: 'Hello World'
}
createMaterial('circle', { id: 'test-002' }),
createMaterial('text', {
id: 'test-003',
text: 'Hello World',
}),
createElement('image', { uuid: 'test-004', detail: { src: 'https://example.com/001.png' } }),
createElement('group', {
uuid: 'test-005',
detail: {
children: [
createElement('rect', { uuid: 'test-006' }),
createElement('circle', { uuid: 'test-007' }),
createElement('text', {
uuid: 'test-008',
detail: {
text: 'Text in Group'
}
}),
createElement('image', { uuid: 'test-009', detail: { src: 'https://example.com/002.png' } })
]
}
})
]
createMaterial('image', { id: 'test-004', src: 'https://example.com/001.png' }),
createMaterial('group', {
id: 'test-005',
children: [
createMaterial('rect', { id: 'test-006' }),
createMaterial('circle', { id: 'test-007' }),
createMaterial('text', {
id: 'test-008',
text: 'Text in Group',
}),
createMaterial('image', { id: 'test-009', src: 'https://example.com/002.png' }),
],
}),
],
});
describe('idraw: useHistory ', () => {
@ -45,50 +37,48 @@ describe('idraw: useHistory ', () => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('modifyElement', () => {
test('modifyMaterial', () => {
const data = createData();
const div = document.createElement('div') as HTMLDivElement;
const idraw = new iDraw(div, {
height: 200,
width: 200
width: 200,
});
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
const targetElement = deepClone(data.elements[0]);
const targetMaterial = deepClone(data.materials[0]);
// modify 1: do
const modifiedInfo1 = {
x: targetElement.x + 1,
y: targetElement.y + 2,
detail: {
background: '#123456',
borderRadius: 3
}
x: targetMaterial.x + 1,
y: targetMaterial.y + 2,
fill: '#123456',
cornerRadius: 3,
};
idraw.modifyElement({
uuid: targetElement.uuid,
...deepClone(modifiedInfo1)
idraw.modifyMaterial({
id: targetMaterial.id,
...deepClone(modifiedInfo1),
});
const expectedData1 = createData();
const flattenModifiedInfo1 = toFlattenElement(modifiedInfo1);
const flattenModifiedInfo1 = toFlattenMaterial(modifiedInfo1);
const beforeInfo1: Record<string, any> = {};
const afterInfo1 = { ...flattenModifiedInfo1 };
Object.keys(flattenModifiedInfo1).forEach((key) => {
beforeInfo1[key] = get(expectedData1.elements[0], key);
set(expectedData1.elements[0], key, flattenModifiedInfo1[key]);
beforeInfo1[key] = get(expectedData1.materials[0], key);
set(expectedData1.materials[0], key, flattenModifiedInfo1[key]);
});
const record1 = {
type: 'modifyElement',
type: 'modifyMaterial',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: beforeInfo1,
after: afterInfo1
}
after: afterInfo1,
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
@ -98,40 +88,38 @@ describe('idraw: useHistory ', () => {
const modifiedInfo2 = {
x: modifiedInfo1.x + 3,
y: modifiedInfo1.y + 4,
detail: {
borderRadius: [2, 4, 6, 8]
}
} as unknown as RecursivePartial<Omit<Element, 'uuid'>>;
cornerRadius: [2, 4, 6, 8],
} as unknown as RecursivePartial<Omit<Material, 'id'>>;
idraw.modifyElement({
uuid: targetElement.uuid,
...deepClone(modifiedInfo2)
} as RecursivePartial<Omit<Element, 'uuid'>> & Pick<Element, 'uuid'>);
idraw.modifyMaterial({
id: targetMaterial.id,
...deepClone(modifiedInfo2),
} as RecursivePartial<Omit<Material, 'id'>> & Pick<Material, 'id'>);
const expectedData2 = deepClone(expectedData1);
const flattenModifiedInfo2 = toFlattenElement(modifiedInfo2);
const flattenModifiedInfo2 = toFlattenMaterial(modifiedInfo2);
const beforeInfo2: Record<string, any> = {};
const afterInfo2 = { ...flattenModifiedInfo2 };
Object.keys(flattenModifiedInfo2).forEach((key) => {
let beforeVal = get(expectedData1.elements[0], key);
let beforeVal = get(expectedData1.materials[0], key);
let beforeKey = key;
if (beforeVal === undefined && /(borderRadius|borderWidth)\[[0-9]{1,}\]$/.test(beforeKey)) {
if (beforeVal === undefined && /(cornerRadius|strokeWidth)\[[0-9]{1,}\]$/.test(beforeKey)) {
beforeKey = beforeKey.replace(/\[[0-9]{1,}\]$/, '');
beforeVal = get(expectedData1.elements[0], beforeKey);
beforeVal = get(expectedData1.materials[0], beforeKey);
}
beforeInfo2[beforeKey] = beforeVal;
set(expectedData2.elements[0], key, flattenModifiedInfo2[key]);
set(expectedData2.materials[0], key, flattenModifiedInfo2[key]);
});
const record2 = {
type: 'modifyElement',
type: 'modifyMaterial',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: beforeInfo2,
after: afterInfo2
}
after: afterInfo2,
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record1, record2]);
@ -143,11 +131,11 @@ describe('idraw: useHistory ', () => {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record2.content.after),
after: deepClone(record2.content.before)
}
after: deepClone(record2.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record1]);
@ -159,11 +147,11 @@ describe('idraw: useHistory ', () => {
type: 'undo',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record1.content.after),
after: deepClone(record1.content.before)
}
after: deepClone(record1.content.before),
},
};
expect(idraw.getData()).toStrictEqual(createData());
expect(__getDoRecords()).toStrictEqual([]);
@ -175,11 +163,11 @@ describe('idraw: useHistory ', () => {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record4.content.after),
after: deepClone(record4.content.before)
}
after: deepClone(record4.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData1);
expect(__getDoRecords()).toStrictEqual([record5]);
@ -191,11 +179,11 @@ describe('idraw: useHistory ', () => {
type: 'redo',
time: new Date().getTime(),
content: {
method: 'modifyElement',
uuid: targetElement.uuid,
method: 'modifyMaterial',
id: targetMaterial.id,
before: deepClone(record3.content.after),
after: deepClone(record3.content.before)
}
after: deepClone(record3.content.before),
},
};
expect(idraw.getData()).toStrictEqual(expectedData2);
expect(__getDoRecords()).toStrictEqual([record5, record6]);

View file

@ -1,48 +0,0 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import * as util from '../src';
const types = {
Context2D: 'Function',
check: 'Object',
createUUID: 'Function',
deepClone: 'Function',
delay: 'Function',
downloadImageFromCanvas: 'Function',
is: 'Object',
isColorStr: 'Function',
istype: 'Object',
loadHTML: 'AsyncFunction',
loadImage: 'Function',
loadSVG: 'AsyncFunction',
throttle: 'Function',
toColorHexNum: 'Function',
toColorHexStr: 'Function'
};
function getType(data: any): string {
const typeStr = Object.prototype.toString.call(data) || '';
const result = typeStr.replace(/(\[object|\])/gi, '').trim();
return result;
}
describe('@idraw/util', () => {
// test('index', async () => {
// const keys = Object.keys(util);
// keys.forEach((key) => {
// // @ts-ignore
// const type = getType(util[key]);
// // @ts-ignore
// expect(type).toStrictEqual(types[key]);
// });
// });
test('index', async () => {
const keys = Object.keys(types);
keys.forEach((key) => {
// @ts-ignore
const type = getType(util[key]);
// @ts-ignore
expect(type).toStrictEqual(types[key]);
});
});
});

View file

@ -1,879 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`@idraw/board: src/lib/context Context 1`] = `
[
{
"props": {
"height": 1800,
"width": 2000,
"x": 0,
"y": 0,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "clearRect",
},
{
"props": {
"height": 1800,
"width": 2000,
"x": 0,
"y": 0,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
{
"props": {
"height": 240,
"width": 400,
"x": 20,
"y": 20,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
{
"props": {
"height": 240,
"width": 400,
"x": 160,
"y": 160,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
{
"props": {
"height": 240,
"width": 400,
"x": 320,
"y": 320,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
{
"props": {
"height": 200,
"width": 400,
"x": 780,
"y": 580,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
]
`;
exports[`@idraw/board: src/lib/context Context.arc 1`] = `
[
{
"props": {
"fillRule": "nonzero",
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
{
"props": {
"anticlockwise": true,
"endAngle": 6.283185307179586,
"radius": 100,
"startAngle": 0,
"x": 140,
"y": 160,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "arc",
},
],
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fill",
},
]
`;
exports[`@idraw/board: src/lib/context Context.arcTo 1`] = `
[
{
"props": {
"fillRule": "nonzero",
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
{
"props": {
"cpx1": 100,
"cpx2": 200,
"cpy1": 100,
"cpy2": 200,
"radius": 12.566370614359172,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "arcTo",
},
],
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fill",
},
]
`;
exports[`@idraw/board: src/lib/context Context.beginPath 1`] = `
[
{
"props": {
"fillRule": "nonzero",
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
],
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fill",
},
]
`;
exports[`@idraw/board: src/lib/context Context.clearRect 1`] = `
[
{
"props": {
"height": 1800,
"width": 2000,
"x": 0,
"y": 0,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "clearRect",
},
]
`;
exports[`@idraw/board: src/lib/context Context.closePath 1`] = `
[
{
"props": {
"fillRule": "nonzero",
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "closePath",
},
],
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fill",
},
]
`;
exports[`@idraw/board: src/lib/context Context.createPattern 1`] = `
[
{
"props": {
"height": 600,
"width": 600,
"x": 0,
"y": 0,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
]
`;
exports[`@idraw/board: src/lib/context Context.drawImage 1`] = `
[
{
"props": {
"dHeight": 0,
"dWidth": 0,
"dx": 22,
"dy": 24,
"img": <img />,
"sHeight": 0,
"sWidth": 0,
"sx": 0,
"sy": 0,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "drawImage",
},
{
"props": {
"dHeight": 104,
"dWidth": 102,
"dx": 22,
"dy": 24,
"img": <img />,
"sHeight": 204,
"sWidth": 202,
"sx": 122,
"sy": 124,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "drawImage",
},
]
`;
exports[`@idraw/board: src/lib/context Context.fill 1`] = `
[
{
"props": {
"fillRule": "nonzero",
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
],
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fill",
},
]
`;
exports[`@idraw/board: src/lib/context Context.fillRect 1`] = `
[
{
"props": {
"height": 200,
"width": 160,
"x": 20,
"y": 40,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
]
`;
exports[`@idraw/board: src/lib/context Context.fillText 1`] = `
[
{
"props": {
"maxWidth": null,
"text": "Hello world",
"x": 100,
"y": 200,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillText",
},
]
`;
exports[`@idraw/board: src/lib/context Context.lineTo 1`] = `
[
{
"props": {
"fillRule": "nonzero",
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
{
"props": {
"x": 20,
"y": 40,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "lineTo",
},
],
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fill",
},
]
`;
exports[`@idraw/board: src/lib/context Context.moveTo 1`] = `
[
{
"props": {
"fillRule": "nonzero",
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
{
"props": {
"x": 20,
"y": 40,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "moveTo",
},
],
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fill",
},
]
`;
exports[`@idraw/board: src/lib/context Context.rect 1`] = `
[
{
"props": {
"fillRule": "nonzero",
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
{
"props": {
"height": 400,
"width": 200,
"x": 20,
"y": 40,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "rect",
},
],
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fill",
},
]
`;
exports[`@idraw/board: src/lib/context Context.restore 1`] = `
[
{
"props": {
"height": 200,
"width": 200,
"x": 20,
"y": 20,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
{
"props": {
"height": 200,
"width": 200,
"x": 300,
"y": 150,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
]
`;
exports[`@idraw/board: src/lib/context Context.rotate 1`] = `
[
{
"props": {
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
{
"props": {
"height": 400,
"width": 200,
"x": 20,
"y": 40,
},
"transform": [
0.8660254037844387,
0.49999999999999994,
-0.49999999999999994,
0.8660254037844387,
0,
0,
],
"type": "rect",
},
],
},
"transform": [
0.8660254037844387,
0.49999999999999994,
-0.49999999999999994,
0.8660254037844387,
0,
0,
],
"type": "stroke",
},
]
`;
exports[`@idraw/board: src/lib/context Context.save 1`] = `
[
{
"props": {
"height": 200,
"width": 200,
"x": 20,
"y": 20,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
{
"props": {
"height": 200,
"width": 200,
"x": 300,
"y": 150,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "fillRect",
},
]
`;
exports[`@idraw/board: src/lib/context Context.scale 1`] = `
[
{
"props": {
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
{
"props": {
"height": 400,
"width": 200,
"x": 20,
"y": 40,
},
"transform": [
2,
0,
0,
3,
0,
0,
],
"type": "rect",
},
],
},
"transform": [
2,
0,
0,
3,
0,
0,
],
"type": "stroke",
},
]
`;
exports[`@idraw/board: src/lib/context Context.stroke 1`] = `
[
{
"props": {
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
{
"props": {
"height": 400,
"width": 200,
"x": 20,
"y": 40,
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "rect",
},
],
},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "stroke",
},
]
`;
exports[`@idraw/board: src/lib/context Context.translate 1`] = `
[
{
"props": {
"path": [
{
"props": {},
"transform": [
1,
0,
0,
1,
0,
0,
],
"type": "beginPath",
},
{
"props": {
"height": 400,
"width": 200,
"x": 20,
"y": 40,
},
"transform": [
1,
0,
0,
1,
100,
120,
],
"type": "rect",
},
],
},
"transform": [
1,
0,
0,
1,
100,
120,
],
"type": "stroke",
},
]
`;

View file

@ -1,3 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`@idraw/util: lib/file downloadImageFromCanvas 1`] = `<canvas />`;

View file

@ -1,21 +0,0 @@
import { toColorHexNum, toColorHexStr, isColorStr } from '@idraw/util';
describe('@idraw/util: lib/color', () => {
const hex = '#f0f0f0';
const num = 15790320;
test('toColorHexNum', async () => {
const result = toColorHexNum(hex);
expect(result).toStrictEqual(num);
});
test('toColorHexStr', async () => {
const result = toColorHexStr(num);
expect(result).toStrictEqual(hex);
});
test('isColorStr', async () => {
const result = isColorStr(hex);
expect(result).toStrictEqual(true);
});
});

View file

@ -1,511 +0,0 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Context2D, deepClone } from '@idraw/util';
import { getData } from './data';
describe('@idraw/board: src/lib/context', () => {
const options = {
width: 600,
height: 400,
contextWidth: 1000,
contextHeight: 900,
devicePixelRatio: 2
};
test('Context', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const data = getData();
ctx.clearRect(0, 0, opts.contextWidth, opts.contextHeight);
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, opts.contextWidth, opts.contextHeight);
data.elements.forEach((ele) => {
ctx.fillStyle = ele.detail.color;
ctx.fillRect(ele.x, ele.y, ele.w, ele.h);
});
// @ts-ignore;
const calls = ctx2d.__getDrawCalls();
expect(calls).toMatchSnapshot();
});
test('Context.setFillStyle', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const color = '#f0f0f0';
ctx.fillStyle = color;
ctx.fillRect(0, 0, opts.contextWidth, opts.contextHeight);
expect(ctx2d.fillStyle).toStrictEqual(color);
});
test('Context.fill', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.fill();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
expect(calls).toMatchSnapshot();
});
test('Context.arc', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.arc(70, 80, 50, 0, Math.PI * 2, true);
ctx.fill();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.rect', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.rect(10, 20, 100, 200);
ctx.fill();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.fillRect', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.fillRect(10, 20, 80, 100);
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
// expect(calls).toStrictEqual([
// {
// type: 'fillRect',
// transform: [ 1, 0, 0, 1, 0, 0 ],
// props: { x: 0, y: 0, width: 2000, height: 1800 }
// }
// ]);
});
test('Context.clearRect', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.clearRect(0, 0, opts.contextWidth, opts.contextHeight);
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.beginPath', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.beginPath();
ctx.fill();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.closePath', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.closePath();
ctx.fill();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.lineTo', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.lineTo(10, 20);
ctx.fill();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.moveTo', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.moveTo(10, 20);
ctx.fill();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.arcTo', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.arcTo(50, 50, 100, 100, Math.PI * 2);
ctx.fill();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.setLineWidth', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const lineWidth = 12;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.lineWidth = lineWidth;
expect(ctx2d.lineWidth).toStrictEqual(lineWidth * opts.devicePixelRatio);
});
test('Context.setLineDash', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const lineDash = [10, 20];
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.setLineDash(lineDash);
// @ts-ignore
// const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
// expect(calls).toMatchSnapshot();
expect(ctx2d.getLineDash()).toStrictEqual(lineDash.map((n) => n * opts.devicePixelRatio));
});
test('Context.setStrokeStyle', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const color = '#f0f0f0';
ctx.strokeStyle = color;
ctx.fillRect(0, 0, opts.contextWidth, opts.contextHeight);
expect(ctx2d.strokeStyle).toStrictEqual(color);
});
// TODO
test('Context.isPointInPath', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
// const lineDash = [10, 20];
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const x = 50;
const y = 50;
const w = 50;
const h = 50;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + w, y);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x, y + h);
ctx.lineTo(x, y);
ctx.closePath();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
expect(ctx.isPointInPath(51, 51)).toStrictEqual(ctx2d.isPointInPath(60 * opts.devicePixelRatio, 60 * opts.devicePixelRatio));
});
test('Context.setStrokeStyle', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const color = '#f0f0f0';
ctx.strokeStyle = color;
expect(ctx2d.strokeStyle).toStrictEqual(color);
});
test('Context.stroke', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.rect(10, 20, 100, 200);
ctx.stroke();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.translate', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const x = 50;
const y = 60;
ctx.translate(x, y);
ctx.rect(10, 20, 100, 200);
ctx.stroke();
ctx.translate(-x, -y);
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
expect(calls).toMatchSnapshot();
});
test('Context.rotate', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const radian = Math.PI / 6;
ctx.rotate(radian);
ctx.rect(10, 20, 100, 200);
ctx.stroke();
ctx.rotate(-radian);
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.drawImage', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const img = new Image();
const dx = 11;
const dy = 12;
const dw = 51;
const dh = 52;
const sx = 61;
const sy = 62;
const sw = 101;
const sh = 102;
ctx.drawImage(img, dx, dy);
ctx.drawImage(img, dx, dy, dw, dh);
ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.createPattern', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const img = new Image();
const pattern = ctx.createPattern(img, 'repeat') as CanvasPattern;
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 300, 300);
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(ctx2d.fillStyle).toStrictEqual(pattern);
expect(calls).toMatchSnapshot();
});
test('Context.measureText', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.$setFont({ fontSize: 20 });
const size = ctx.measureText('Hello World!');
expect(size.width).toStrictEqual(12);
});
test('Context.setTextAlign', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const textAlign = 'center';
ctx.textAlign = textAlign;
ctx.fillRect(0, 0, opts.contextWidth, opts.contextHeight);
expect(ctx2d.textAlign).toStrictEqual(textAlign);
});
test('Context.fillText', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.fillText('Hello world', 50, 100);
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.$setFont', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const fontSize = 22;
const fontFamily = 'Hello';
const fontWeight = 'bold';
ctx.$setFont({
fontSize,
fontFamily,
fontWeight
});
// console.log('ctx2d.font =', ctx2d.font);
expect(ctx2d.font).toStrictEqual([fontWeight, fontSize * opts.devicePixelRatio + 'px', fontFamily].join(' '));
});
test('Context.textBaseline', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const textBaseline = 'bottom';
ctx.textBaseline = textBaseline;
expect(ctx2d.textBaseline).toStrictEqual(textBaseline);
});
test('Context.globalAlpha', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const globalAlpha = 0.45;
ctx.globalAlpha = globalAlpha;
expect(ctx2d.globalAlpha).toStrictEqual(globalAlpha);
});
test('Context.save', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.save();
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(10, 10, 100, 100);
ctx.restore();
ctx.fillRect(150, 75, 100, 100);
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.restore', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
ctx.save();
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(10, 10, 100, 100);
ctx.restore();
ctx.fillRect(150, 75, 100, 100);
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
test('Context.scale', async () => {
const opts = deepClone(options);
const canvas = document.createElement('canvas');
canvas.width = opts.contextWidth;
canvas.height = opts.contextHeight;
const ctx2d: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
const ctx = new Context2D(ctx2d, opts);
const scaleX = 2;
const scaleY = 3;
ctx.scale(scaleX, scaleY);
ctx.rect(10, 20, 100, 200);
ctx.stroke();
// @ts-ignore
const calls = ctx2d.__getDrawCalls();
// console.log('calls =', JSON.stringify(calls, null, 2));
expect(calls).toMatchSnapshot();
});
});

View file

@ -3,53 +3,45 @@ import type { Data } from '@idraw/types';
import { imageBase64, html, svg } from '../_assets/base';
const originData: Data = {
elements: [
materials: [
{
uuid: 'b37213ce-d711-cbb3-51ac-d8081c19f127',
id: 'b37213ce-d711-cbb3-51ac-d8081c19f127',
type: 'image',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
src: imageBase64
}
width: 100,
height: 100,
href: imageBase64,
},
{
uuid: '063e3a80-1ede-7912-f919-975e34a9bd01',
id: '063e3a80-1ede-7912-f919-975e34a9bd01',
type: 'group',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
children: [
{
uuid: 'b60e64e8-833e-e112-d7eb-1ab6e7d6870c',
type: 'svg',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
svg: svg
}
},
{
uuid: '61f2a61e-cdd5-ae36-983f-686ba8e35973',
type: 'html',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
html: html
}
}
]
}
}
]
width: 100,
height: 100,
children: [
{
id: 'b60e64e8-833e-e112-d7eb-1ab6e7d6870c',
type: 'foreignObject',
x: 0,
y: 0,
width: 100,
height: 100,
content: svg,
},
{
id: '61f2a61e-cdd5-ae36-983f-686ba8e35973',
type: 'foreignObject',
x: 0,
y: 0,
width: 100,
height: 100,
content: html,
},
],
},
],
};
describe('@idraw/util: data ', () => {
@ -58,61 +50,59 @@ describe('@idraw/util: data ', () => {
const compactData = filterCompactData(data);
const expectData: Data = {
elements: [
materials: [
{
uuid: 'b37213ce-d711-cbb3-51ac-d8081c19f127',
id: 'b37213ce-d711-cbb3-51ac-d8081c19f127',
type: 'image',
x: 0,
y: 0,
w: 100,
h: 100,
detail: { src: '@assets/0a920a91-0aba-0af3-0aeb-0a730accafb' }
width: 100,
height: 100,
href: '@assets/0a920a91-0aba-0af3-0aeb-0a730accafb',
},
{
uuid: '063e3a80-1ede-7912-f919-975e34a9bd01',
id: '063e3a80-1ede-7912-f919-975e34a9bd01',
type: 'group',
x: 0,
y: 0,
w: 100,
h: 100,
detail: {
children: [
{
uuid: 'b60e64e8-833e-e112-d7eb-1ab6e7d6870c',
type: 'svg',
x: 0,
y: 0,
w: 100,
h: 100,
detail: { svg: '@assets/0a830ab3-0a5d-0a5b-0a63-0a740a6cb34' }
},
{
uuid: '61f2a61e-cdd5-ae36-983f-686ba8e35973',
type: 'html',
x: 0,
y: 0,
w: 100,
h: 100,
detail: { html: '@assets/0a2b0ab4-0b45-0b19-0a0d-0add0a0dab5' }
}
]
}
}
width: 100,
height: 100,
children: [
{
id: 'b60e64e8-833e-e112-d7eb-1ab6e7d6870c',
type: 'foreignObject',
x: 0,
y: 0,
width: 100,
height: 100,
content: '@assets/0a830ab3-0a5d-0a5b-0a63-0a740a6cb34',
},
{
id: '61f2a61e-cdd5-ae36-983f-686ba8e35973',
type: 'foreignObject',
x: 0,
y: 0,
width: 100,
height: 100,
content: '@assets/0a2b0ab4-0b45-0b19-0a0d-0add0a0dab5',
},
],
},
],
assets: {
'@assets/0a920a91-0aba-0af3-0aeb-0a730accafb': {
type: 'image',
value: imageBase64
value: imageBase64,
},
'@assets/0a830ab3-0a5d-0a5b-0a63-0a740a6cb34': {
type: 'svg',
value: svg
type: 'foreignObject',
value: svg,
},
'@assets/0a2b0ab4-0b45-0b19-0a0d-0add0a0dab5': {
type: 'html',
value: html
}
}
type: 'foreignObject',
value: html,
},
},
};
expect(compactData).toStrictEqual(expectData);

View file

@ -1,47 +0,0 @@
export function getData() {
const data = {
elements: [
{
x: 10,
y: 10,
w: 200,
h: 120,
type: 'rect',
detail: {
color: '#f0f0f0'
}
},
{
x: 80,
y: 80,
w: 200,
h: 120,
type: 'rect',
detail: {
color: '#cccccc'
}
},
{
x: 160,
y: 160,
w: 200,
h: 120,
type: 'rect',
detail: {
color: '#c0c0c0'
}
},
{
x: 400 - 10,
y: 300 - 10,
w: 200,
h: 100,
type: 'rect',
detail: {
color: '#e0e0e0'
}
}
]
};
return data;
}

View file

@ -1,59 +0,0 @@
import { createUUID, getElementPositionFromList } from '@idraw/util';
import type { Elements } from '@idraw/types';
const getElemBase = () => {
return {
x: 0,
y: 0,
w: 1,
h: 1
};
};
function generateElements(list: any[]): Elements {
const elements: Elements = list.map((item, i) => {
if (Array.isArray(item)) {
return {
...getElemBase(),
uuid: `${i}-${createUUID()}`,
type: 'group',
detail: {
children: generateElements(item)
}
};
} else {
return {
...getElemBase(),
uuid: `${i}-${createUUID()}`,
type: 'rect',
detail: {}
};
}
}) as Elements;
return elements;
}
describe('@idraw/util: element ', () => {
// [4]
test('getElementPositionFromList [4]', () => {
const list: Elements = generateElements([0, [0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5], 2, 3, 4, 5]);
const uuid = (list as any)[4].uuid;
const position = getElementPositionFromList(uuid, list);
expect(position).toStrictEqual([4]);
});
// [1, 2, 3, 4, 5]
test('getElementPositionFromList [1, 2, 3, 4, 5]', () => {
const list: Elements = generateElements([0, [0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5], 2, 3, 4, 5]);
const uuid = (list as any)[1].detail.children[2].detail.children[3].detail.children[4].detail.children[5].uuid;
const position = getElementPositionFromList(uuid, list);
expect(position).toStrictEqual([1, 2, 3, 4, 5]);
});
// [1, 2, 3, 4]
test('getElementPositionFromList [1, 2, 3, 4, 5]', () => {
const list: Elements = generateElements([0, [0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5], 2, 3, 4, 5]);
const uuid = (list as any)[1].detail.children[2].detail.children[3].detail.children[4].uuid;
const position = getElementPositionFromList(uuid, list);
expect(position).toStrictEqual([1, 2, 3, 4]);
});
});

View file

@ -1,21 +0,0 @@
import { downloadImageFromCanvas } from '@idraw/util';
describe('@idraw/util: lib/file', () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
const opts = {
width: 600,
height: 400
};
ctx.clearRect(0, 0, opts.width, opts.height);
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, opts.width / 2, opts.height / 2);
test('downloadImageFromCanvas', async () => {
downloadImageFromCanvas(canvas, {
fileName: 'hello',
type: 'image/png'
});
expect(canvas).toMatchSnapshot();
});
});

View file

@ -1,92 +1,90 @@
import type { Element } from '@idraw/types';
import { groupElementsByPosition, ungroupElementsByPosition, insertElementToListByPosition } from '@idraw/util';
import type { Material } from '@idraw/types';
import { groupMaterialsByPosition, ungroupMaterialsByPosition } from '@idraw/util';
const createList = () => {
const list: Element[] = [
{ uuid: '0', type: 'rect', x: 20, y: 20, w: 50, h: 50, detail: {} },
{ uuid: '1', type: 'rect', x: 40, y: 40, w: 50, h: 50, detail: {} },
{ uuid: '2', type: 'rect', x: 60, y: 60, w: 50, h: 50, detail: {} },
{ uuid: '3', type: 'rect', x: 80, y: 80, w: 50, h: 50, detail: {} },
{ uuid: '4', type: 'rect', x: 100, y: 100, w: 50, h: 50, detail: {} }
const list: Material[] = [
{ id: '0', type: 'rect', x: 20, y: 20, width: 50, height: 50 },
{ id: '1', type: 'rect', x: 40, y: 40, width: 50, height: 50 },
{ id: '2', type: 'rect', x: 60, y: 60, width: 50, height: 50 },
{ id: '3', type: 'rect', x: 80, y: 80, width: 50, height: 50 },
{ id: '4', type: 'rect', x: 100, y: 100, width: 50, height: 50 },
];
return list;
};
const createGroupedList = () => {
const list: Element[] = [
{ uuid: '0', type: 'rect', x: 20, y: 20, w: 50, h: 50, detail: {} },
const list: Material[] = [
{ id: '0', type: 'rect', x: 20, y: 20, width: 50, height: 50 },
{
name: 'Group',
uuid: 'group-id',
id: 'group-id',
type: 'group',
x: 40,
y: 40,
w: 90,
h: 90,
detail: {
children: [
{ uuid: '1', type: 'rect', x: 0, y: 0, w: 50, h: 50, detail: {} },
{ uuid: '2', type: 'rect', x: 20, y: 20, w: 50, h: 50, detail: {} },
{ uuid: '3', type: 'rect', x: 40, y: 40, w: 50, h: 50, detail: {} }
]
}
width: 90,
height: 90,
children: [
{ id: '1', type: 'rect', x: 0, y: 0, width: 50, height: 50 },
{ id: '2', type: 'rect', x: 20, y: 20, width: 50, height: 50 },
{ id: '3', type: 'rect', x: 40, y: 40, width: 50, height: 50 },
],
},
{ uuid: '4', type: 'rect', x: 100, y: 100, w: 50, h: 50, detail: {} }
{ id: '4', type: 'rect', x: 100, y: 100, width: 50, height: 50 },
];
return list;
};
describe('@idraw/util: group ', () => {
test('groupElementsByPosition', () => {
test('groupMaterialsByPosition', () => {
const list = createList();
groupElementsByPosition(list, [[1], [2], [3]]);
list[1].uuid = 'group-id';
groupMaterialsByPosition(list, [[1], [2], [3]]);
list[1].id = 'group-id';
expect(list).toStrictEqual(createGroupedList());
});
test('groupElementsByPosition with disordered positions', () => {
test('groupMaterialsByPosition with disordered positions', () => {
const list = createList();
groupElementsByPosition(list, [[3], [1], [2]]);
list[1].uuid = 'group-id';
groupMaterialsByPosition(list, [[3], [1], [2]]);
list[1].id = 'group-id';
expect(list).toStrictEqual(createGroupedList());
});
test('groupElementsByPosition with deep position', () => {
test('groupMaterialsByPosition with deep position', () => {
const list = createList();
list[0].type = 'group';
list[0].detail = { children: createList() };
groupElementsByPosition(list, [
list[0].children = createList();
groupMaterialsByPosition(list, [
[0, 1],
[0, 2],
[0, 3]
[0, 3],
]);
list[0].detail.children[1].uuid = 'group-id';
list[0].children[1].id = 'group-id';
const expectedList = createList();
expectedList[0].type = 'group';
expectedList[0].detail = { children: createGroupedList() };
expectedList[0].children = createGroupedList();
expect(list).toStrictEqual(expectedList);
});
test('groupElementsByPosition with deep position and disordered positions', () => {
test('groupMaterialsByPosition with deep position and disordered positions', () => {
const list = createList();
list[0].type = 'group';
list[0].detail = { children: createList() };
groupElementsByPosition(list, [
list[0].children = createList();
groupMaterialsByPosition(list, [
[0, 3],
[0, 1],
[0, 2]
[0, 2],
]);
list[0].detail.children[1].uuid = 'group-id';
list[0].children[1].id = 'group-id';
const expectedList = createList();
expectedList[0].type = 'group';
expectedList[0].detail = { children: createGroupedList() };
expectedList[0].children = createGroupedList();
expect(list).toStrictEqual(expectedList);
});
test('upgroupElementsByPosition', () => {
test('upgroupMaterialsByPosition', () => {
const list = createGroupedList();
ungroupElementsByPosition(list, [1]);
ungroupMaterialsByPosition(list, [1]);
expect(list).toStrictEqual(createList());
});
});

View file

@ -1,130 +0,0 @@
import { moveElementPosition } from '@idraw/util';
import type { Elements } from '@idraw/types';
const getElemBase = () => {
return {
x: 0,
y: 0,
w: 1,
h: 1
};
};
function generateElements(list: any[]): Elements {
const elements: Elements = list.map((item) => {
if (Array.isArray(item)) {
return {
...getElemBase(),
uuid: `group`,
type: 'group',
detail: {
children: generateElements(item)
}
};
} else {
return {
...getElemBase(),
uuid: `rect-${item}`,
type: 'rect',
detail: {}
};
}
}) as Elements;
return elements;
}
describe('@idraw/util: handle-element ', () => {
// [2] -> [4]
// [0, 1, 2, 3, 2, 4, 5]
// [0, 1, 3, 2, 4, 5]
test('moveElementPosition, move-down [2] -> [4]', () => {
const list: Elements = generateElements([0, 1, 2, 3, 4, 5]);
moveElementPosition(list, {
from: [2],
to: [4]
});
const expectResult = generateElements([0, 1, 3, 2, 4, 5]);
expect(list).toStrictEqual(expectResult);
});
// [4] -> [2] yes
// [0, 1, 4, 2, 3, 4, 5]
// [0, 1, 4, 2, 3, 5]
test('moveElementPosition, move-up [4] -> [2]', () => {
const list: Elements = generateElements([0, 1, 2, 3, 4, 5]);
moveElementPosition(list, {
from: [4],
to: [2]
});
const expectResult = generateElements([0, 1, 4, 2, 3, 5]);
expect(list).toStrictEqual(expectResult);
});
// [3, 2, 1] -> [2]
test('moveElementPosition, move-up [3, 2, 1] -> [2]', () => {
const list: Elements = generateElements([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
moveElementPosition(list, {
from: [3, 2, 1],
to: [2]
});
const expectResult = generateElements([0, 1, 1, 2, [0, 1, [0, 2, 3], 3], 4, 5]);
expect(list).toStrictEqual(expectResult);
});
// [1] -> [1, 2, 3]
test('moveElementPosition, move-up [1] -> [1, 2, 3]', () => {
const list: Elements = generateElements([0, [0, 1, [0, 1, 2, 3, 4, 5], 3, 4, 5], 2, 3, 4, 5]);
moveElementPosition(list, {
from: [1],
to: [1, 2, 3]
});
const expectResult = generateElements([0, [0, 1, [0, 1, 2, 3, 4, 5], 3, 4, 5], 2, 3, 4, 5]);
expect(list).toStrictEqual(expectResult);
});
// [1, 2, 3, 4, 5] -> [1, 2, 2]
test('moveElementPosition, move-up [1, 2, 3, 4, 5] -> [1, 2, 2]', () => {
const list: Elements = generateElements([0, [0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5], 2, 3, 4, 5]);
moveElementPosition(list, {
from: [1, 2, 3, 4, 5],
to: [1, 2, 2]
});
const expectResult = generateElements([0, [0, 1, [0, 1, 5, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4], 5], 4, 5], 3, 4, 5], 2, 3, 4, 5]);
expect(list).toStrictEqual(expectResult);
});
// [1] -> [1]
test('moveElementPosition, move-up [1] -> [1]', () => {
const list: Elements = generateElements([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
moveElementPosition(list, {
from: [1],
to: [1]
});
const expectResult = generateElements([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
expect(list).toStrictEqual(expectResult);
});
// [2, 4] -> [1, 2]
test('moveElementPosition, move-up [1, 2] -> [2, 4]', () => {
const list: Elements = generateElements([0, [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], 3, 4]);
moveElementPosition(list, {
from: [2, 4],
to: [1, 2]
});
const expectResult = generateElements([0, [0, 1, 4, 2, 3, 4], [0, 1, 2, 3], 3, 4]);
expect(list).toStrictEqual(expectResult);
});
});

View file

@ -0,0 +1,141 @@
import { moveMaterialPosition } from '@idraw/util';
import type { StrictMaterial } from '@idraw/types';
const getElemBase = () => {
return {
x: 0,
y: 0,
width: 1,
height: 1,
};
};
function generateMaterials(list: any[]): StrictMaterial[] {
const elements: StrictMaterial[] = list.map((item) => {
if (Array.isArray(item)) {
return {
...getElemBase(),
id: `group`,
type: 'group',
children: generateMaterials(item),
};
} else {
return {
...getElemBase(),
id: `rect-${item}`,
type: 'rect',
};
}
}) as StrictMaterial[];
return elements;
}
describe('@idraw/util: handle-element ', () => {
// [2] -> [4]
// [0, 1, 2, 3, 2, 4, 5]
// [0, 1, 3, 2, 4, 5]
test('moveMaterialPosition, move-down [2] -> [4]', () => {
const list: StrictMaterial[] = generateMaterials([0, 1, 2, 3, 4, 5]);
moveMaterialPosition(list, {
from: [2],
to: [4],
});
const expectResult = generateMaterials([0, 1, 3, 2, 4, 5]);
expect(list).toStrictEqual(expectResult);
});
// [4] -> [2] yes
// [0, 1, 4, 2, 3, 4, 5]
// [0, 1, 4, 2, 3, 5]
test('moveMaterialPosition, move-up [4] -> [2]', () => {
const list: StrictMaterial[] = generateMaterials([0, 1, 2, 3, 4, 5]);
moveMaterialPosition(list, {
from: [4],
to: [2],
});
const expectResult = generateMaterials([0, 1, 4, 2, 3, 5]);
expect(list).toStrictEqual(expectResult);
});
// [3, 2, 1] -> [2]
test('moveMaterialPosition, move-up [3, 2, 1] -> [2]', () => {
const list: StrictMaterial[] = generateMaterials([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
moveMaterialPosition(list, {
from: [3, 2, 1],
to: [2],
});
const expectResult = generateMaterials([0, 1, 1, 2, [0, 1, [0, 2, 3], 3], 4, 5]);
expect(list).toStrictEqual(expectResult);
});
// [1] -> [1, 2, 3]
test('moveMaterialPosition, move-up [1] -> [1, 2, 3]', () => {
const list: StrictMaterial[] = generateMaterials([0, [0, 1, [0, 1, 2, 3, 4, 5], 3, 4, 5], 2, 3, 4, 5]);
moveMaterialPosition(list, {
from: [1],
to: [1, 2, 3],
});
const expectResult = generateMaterials([0, [0, 1, [0, 1, 2, 3, 4, 5], 3, 4, 5], 2, 3, 4, 5]);
expect(list).toStrictEqual(expectResult);
});
// [1, 2, 3, 4, 5] -> [1, 2, 2]
test('moveMaterialPosition, move-up [1, 2, 3, 4, 5] -> [1, 2, 2]', () => {
const list: StrictMaterial[] = generateMaterials([
0,
[0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5],
2,
3,
4,
5,
]);
moveMaterialPosition(list, {
from: [1, 2, 3, 4, 5],
to: [1, 2, 2],
});
const expectResult = generateMaterials([
0,
[0, 1, [0, 1, 5, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4], 5], 4, 5], 3, 4, 5],
2,
3,
4,
5,
]);
expect(list).toStrictEqual(expectResult);
});
// [1] -> [1]
test('moveMaterialPosition, move-up [1] -> [1]', () => {
const list: StrictMaterial[] = generateMaterials([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
moveMaterialPosition(list, {
from: [1],
to: [1],
});
const expectResult = generateMaterials([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
expect(list).toStrictEqual(expectResult);
});
// [2, 4] -> [1, 2]
test('moveMaterialPosition, move-up [1, 2] -> [2, 4]', () => {
const list: StrictMaterial[] = generateMaterials([0, [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], 3, 4]);
moveMaterialPosition(list, {
from: [2, 4],
to: [1, 2],
});
const expectResult = generateMaterials([0, [0, 1, 4, 2, 3, 4], [0, 1, 2, 3], 3, 4]);
expect(list).toStrictEqual(expectResult);
});
});

View file

@ -1,76 +0,0 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { istype } from '@idraw/util';
describe('@idraw/util: lib/istype', () => {
const _num = 123;
const _str = 'abc';
const _null = null;
const _undefined = undefined;
const _arr = [0, 1, 2, 3];
const _json = {
a: [0, 1, 2, 3],
b: {
c: 123
}
};
const _func = function () {};
const _asyncfunc = async function () {};
const _promise = new Promise(() => {});
test('istype.type', async () => {
expect(istype.type(_num)).toStrictEqual('Number');
expect(istype.type(_str)).toStrictEqual('String');
expect(istype.type(_undefined)).toStrictEqual('Undefined');
expect(istype.type(_arr)).toStrictEqual('Array');
expect(istype.type(_null)).toStrictEqual('Null');
expect(istype.type(_json)).toStrictEqual('Object');
expect(istype.type(_func)).toStrictEqual('Function');
expect(istype.type(_asyncfunc)).toStrictEqual('AsyncFunction');
expect(istype.type(_promise)).toStrictEqual('Promise');
});
test('istype.array', async () => {
expect(istype.array(_arr)).toStrictEqual(true);
expect(istype.array(null)).toStrictEqual(false);
});
test('istype.json', async () => {
expect(istype.json(_json)).toStrictEqual(true);
expect(istype.json(null)).toStrictEqual(false);
});
test('istype.function', async () => {
expect(istype.function(_func)).toStrictEqual(true);
expect(istype.function(null)).toStrictEqual(false);
});
test('istype.asyncFunction', async () => {
expect(istype.asyncFunction(_asyncfunc)).toStrictEqual(true);
expect(istype.asyncFunction(null)).toStrictEqual(false);
});
test('istype.string', async () => {
expect(istype.string(_str)).toStrictEqual(true);
expect(istype.string(null)).toStrictEqual(false);
});
test('istype.number', async () => {
expect(istype.number(_num)).toStrictEqual(true);
expect(istype.number(null)).toStrictEqual(false);
});
test('istype.undefined', async () => {
expect(istype.undefined(_undefined)).toStrictEqual(true);
expect(istype.undefined(null)).toStrictEqual(false);
});
test('istype.null', async () => {
expect(istype.null(_null)).toStrictEqual(true);
expect(istype.null(1)).toStrictEqual(false);
});
test('istype.promise', async () => {
expect(istype.promise(_promise)).toStrictEqual(true);
expect(istype.promise(null)).toStrictEqual(false);
});
});

View file

@ -1,71 +0,0 @@
import '../../../../__tests__/polyfill/image';
import { loadHTML, loadImage, loadSVG } from '@idraw/util';
import { parseHTMLToDataURL, parseSVGToDataURL } from '../../src/view/parser';
describe('@idraw/util: lib/loader', () => {
test('loadHTML', async () => {
const html = `
<style>
.btn-box {
margin: 10px 0;
}
.btn {
line-height: 1.5715;
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
box-shadow: 0 2px #00000004;
cursor: pointer;
user-select: none;
height: 32px;
padding: 4px 15px;
font-size: 14px;
border-radius: 2px;
color: #000000d9;
background: #fff;
border-color: #d9d9d9;
}
.btn-primary {
color: #fff;
background: #1890ff;
border-color: #1890ff;
text-shadow: 0 -1px 0 rgb(0 0 0 / 12%);
box-shadow: 0 2px #0000000b;
}
</style>
<div>
<div class="btn-box" style="margin-top: 0;">
<button class="btn" >
<span>Button</span>
</button>
</div>
<div class="btn-box">
<button class="btn btn-primary">
<span>Button Primary</span>
</button>
</div>
</div>
`;
const opts = {
width: 120,
height: 80
};
const result = await loadHTML(html, opts);
const expectDataURL = await parseHTMLToDataURL(html, opts);
const expectImage = await loadImage(expectDataURL);
expect(result.src).toStrictEqual(expectImage.src);
});
test('loadSVG', async () => {
const svg = `<svg t="1622524780663" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8365" width="200" height="200"><path d="M881 442.4H519.7v148.5h206.4c-8.9 48-35.9 88.6-76.6 115.8-34.4 23-78.3 36.6-129.9 36.6-99.9 0-184.4-67.5-214.6-158.2-7.6-23-12-47.6-12-72.9s4.4-49.9 12-72.9c30.3-90.6 114.8-158.1 214.7-158.1 56.3 0 106.8 19.4 146.6 57.4l110-110.1c-66.5-62-153.2-100-256.6-100-149.9 0-279.6 86-342.7 211.4-26 51.8-40.8 110.4-40.8 172.4S151 632.8 177 684.6C240.1 810 369.8 896 519.7 896c103.6 0 190.4-34.4 253.8-93 72.5-66.8 114.4-165.2 114.4-282.1 0-27.2-2.4-53.3-6.9-78.5z" p-id="8366" fill="#1296db"></path></svg>`;
const result = await loadSVG(svg);
const expectDataURL = await parseSVGToDataURL(svg);
const expectImage = await loadImage(expectDataURL);
expect(result.src).toStrictEqual(expectImage.src);
});
});

View file

@ -0,0 +1,77 @@
import { createUUID, getMaterialPositionFromList } from '@idraw/util';
import type { StrictMaterial } from '@idraw/types';
const getElemBase = () => {
return {
x: 0,
y: 0,
width: 1,
height: 1,
};
};
function generateMaterials(list: any[]): StrictMaterial[] {
const elements: StrictMaterial[] = list.map((item, i) => {
if (Array.isArray(item)) {
return {
...getElemBase(),
id: `${i}-${createUUID()}`,
type: 'group',
children: generateMaterials(item),
};
} else {
return {
...getElemBase(),
id: `${i}-${createUUID()}`,
type: 'rect',
};
}
}) as StrictMaterial[];
return elements;
}
describe('@idraw/util: element ', () => {
// [4]
test('getMaterialPositionFromList [4]', () => {
const list: StrictMaterial[] = generateMaterials([
0,
[0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5],
2,
3,
4,
5,
]);
const id = (list as any)[4].id;
const position = getMaterialPositionFromList(id, list);
expect(position).toStrictEqual([4]);
});
// [1, 2, 3, 4, 5]
test('getMaterialPositionFromList [1, 2, 3, 4, 5]', () => {
const list: StrictMaterial[] = generateMaterials([
0,
[0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5],
2,
3,
4,
5,
]);
const id = (list as any)[1].children[2].children[3].children[4].children[5].id;
const position = getMaterialPositionFromList(id, list);
expect(position).toStrictEqual([1, 2, 3, 4, 5]);
});
// [1, 2, 3, 4]
test('getMaterialPositionFromList [1, 2, 3, 4, 5]', () => {
const list: StrictMaterial[] = generateMaterials([
0,
[0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5],
2,
3,
4,
5,
]);
const id = (list as any)[1].children[2].children[3].children[4].id;
const position = getMaterialPositionFromList(id, list);
expect(position).toStrictEqual([1, 2, 3, 4]);
});
});

View file

@ -0,0 +1,34 @@
import type { Material } from '@idraw/types';
import { mergeMaterial } from '@idraw/util';
const material: Material = {
id: 'z001zeaa-5c52-590c-f8a2-d2d89d25471f',
type: 'rect',
x: 0,
y: 0,
width: 200,
height: 200,
fill: {
type: 'linear-gradient',
start: { x: 0, y: 0 },
end: { x: 0, y: 200 },
stops: [
{ offset: 0, color: '#009688' },
{ offset: 0.5, color: '#ffeb3b' },
{ offset: 1, color: '#ff5722' },
],
},
};
describe('@idraw/util mergeMaterial', () => {
test('mergeMaterial', () => {
const result = mergeMaterial(material, {
fill: {
stops: [
{ offset: 0, color: '#009688' },
{ offset: 1, color: '#ff5722' },
],
},
});
});
});

View file

@ -1,199 +0,0 @@
import { moveElementPosition, calcRevertMovePosition } from '@idraw/util';
import type { Elements } from '@idraw/types';
const getElemBase = () => {
return {
x: 0,
y: 0,
w: 1,
h: 1
};
};
function generateElements(list: any[]): Elements {
const elements: Elements = list.map((item) => {
if (Array.isArray(item)) {
return {
...getElemBase(),
uuid: `group`,
type: 'group',
detail: {
children: generateElements(item)
}
};
} else {
return {
...getElemBase(),
uuid: `rect-${item}`,
type: 'rect',
detail: {}
};
}
}) as Elements;
return elements;
}
describe('@idraw/util: handle-element ', () => {
// [2] -> [4]
// [0, 1, 2, 3, 2, 4, 5]
// [0, 1, 3, 2, 4, 5]
test('moveElementPosition, move-down [2] -> [4]', () => {
const list: Elements = generateElements([0, 1, 2, 3, 4, 5]);
const from = [2];
const to = [4];
moveElementPosition(list, {
from,
to
});
const expectResult = generateElements([0, 1, 3, 2, 4, 5]);
expect(list).toStrictEqual(expectResult);
// revert action
// result [2] [3]
const revertInfo = calcRevertMovePosition({ from, to }) as { from: number[]; to: number[] };
// revert [3] -> [2]
moveElementPosition(list, {
from: revertInfo.from,
to: revertInfo.to
});
expect(list).toStrictEqual(generateElements([0, 1, 2, 3, 4, 5]));
});
// [4] -> [2] yes
// [0, 1, 4, 2, 3, 4, 5]
// [0, 1, 4, 2, 3, 5]
test('moveElementPosition, move-up [4] -> [2]', () => {
const list: Elements = generateElements([0, 1, 2, 3, 4, 5]);
const from = [4];
const to = [2];
moveElementPosition(list, {
from,
to
});
const expectResult = generateElements([0, 1, 4, 2, 3, 5]);
expect(list).toStrictEqual(expectResult);
// revert action
// result [5] [2]
const revertInfo = calcRevertMovePosition({ from, to }) as { from: number[]; to: number[] };
// revert [2] -> [5]
moveElementPosition(list, {
from: revertInfo.from,
to: revertInfo.to
});
expect(list).toStrictEqual(generateElements([0, 1, 2, 3, 4, 5]));
});
// [3, 2, 1] -> [2]
test('moveElementPosition, move-up [3, 2, 1] -> [2]', () => {
const list: Elements = generateElements([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
const from = [3, 2, 1];
const to = [2];
moveElementPosition(list, {
from,
to
});
const expectResult = generateElements([0, 1, 1, 2, [0, 1, [0, 2, 3], 3], 4, 5]);
expect(list).toStrictEqual(expectResult);
// revert action
// result from: [ 4, 2, 1 ], to: [ 2 ]
const revertInfo = calcRevertMovePosition({ from, to }) as { from: number[]; to: number[] };
// revert [2] -> [ 4, 2, 1 ]
moveElementPosition(list, {
from: revertInfo.from,
to: revertInfo.to
});
expect(list).toStrictEqual(generateElements([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]));
});
// [1] -> [1, 2, 3]
test('moveElementPosition, move-up [1] -> [1, 2, 3]', () => {
const list: Elements = generateElements([0, [0, 1, [0, 1, 2, 3, 4, 5], 3, 4, 5], 2, 3, 4, 5]);
const from = [1];
const to = [1, 2, 3];
moveElementPosition(list, {
from,
to
});
const expectResult = generateElements([0, [0, 1, [0, 1, 2, 3, 4, 5], 3, 4, 5], 2, 3, 4, 5]);
expect(list).toStrictEqual(expectResult);
// revert action null
const revertInfo = calcRevertMovePosition({ from, to });
expect(revertInfo).toBeNull();
});
// [1, 2, 3, 4, 5] -> [1, 2, 2]
test('moveElementPosition, move-up [1, 2, 3, 4, 5] -> [1, 2, 2]', () => {
const list: Elements = generateElements([
0,
[0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5],
2,
3,
4,
5
]);
const from = [1, 2, 3, 4, 5];
const to = [1, 2, 2];
moveElementPosition(list, {
from,
to
});
const expectResult = generateElements([
0,
[0, 1, [0, 1, 5, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4], 5], 4, 5], 3, 4, 5],
2,
3,
4,
5
]);
expect(list).toStrictEqual(expectResult);
// revert action
// result from: [ 1, 2, 4, 4, 5 ], to: [ 1, 2, 2 ]
const revertInfo = calcRevertMovePosition({ from, to }) as { from: number[]; to: number[] };
// revert [ 1, 2, 2 ] -> [ 1, 2, 4, 4, 5 ]
moveElementPosition(list, {
from: revertInfo.from,
to: revertInfo.to
});
expect(list).toStrictEqual(
generateElements([0, [0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5], 2, 3, 4, 5])
);
});
// [1] -> [1]
test('moveElementPosition, move-up [1] -> [1]', () => {
const list: Elements = generateElements([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
const from = [1];
const to = [1];
moveElementPosition(list, {
from,
to
});
const expectResult = generateElements([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
expect(list).toStrictEqual(expectResult);
// revert action null
const revertInfo = calcRevertMovePosition({ from, to });
expect(revertInfo).toBeNull();
});
// [2, 4] -> [1, 2]
test('moveElementPosition, move-up [1, 2] -> [2, 4]', () => {
const list: Elements = generateElements([0, [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], 3, 4]);
const from = [2, 4];
const to = [1, 2];
moveElementPosition(list, {
from,
to
});
const expectResult = generateElements([0, [0, 1, 4, 2, 3, 4], [0, 1, 2, 3], 3, 4]);
expect(list).toStrictEqual(expectResult);
// revert action
// result from: [ 2, 4 ], to: [ 1, 2 ]
const revertInfo = calcRevertMovePosition({ from, to }) as { from: number[]; to: number[] };
// revert [ 1, 2 ] -> [ 2, 4 ]
moveElementPosition(list, {
from: revertInfo.from,
to: revertInfo.to
});
expect(list).toStrictEqual(generateElements([0, [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], 3, 4]));
});
});

View file

@ -0,0 +1,196 @@
import { moveMaterialPosition, calcRevertMovePosition } from '@idraw/util';
import type { StrictMaterial } from '@idraw/types';
const getMaterialBase = () => {
return {
x: 0,
y: 0,
width: 1,
height: 1,
};
};
function generateMaterials(list: any[]): StrictMaterial[] {
const elements: StrictMaterial[] = list.map((item) => {
if (Array.isArray(item)) {
return {
...getMaterialBase(),
id: `group`,
type: 'group',
children: generateMaterials(item),
};
} else {
return {
...getMaterialBase(),
id: `rect-${item}`,
type: 'rect',
};
}
}) as StrictMaterial[];
return elements;
}
describe('@idraw/util: handle-element ', () => {
// [2] -> [4]
// [0, 1, 2, 3, 2, 4, 5]
// [0, 1, 3, 2, 4, 5]
test('moveMaterialPosition, move-down [2] -> [4]', () => {
const list: StrictMaterial[] = generateMaterials([0, 1, 2, 3, 4, 5]);
const from = [2];
const to = [4];
moveMaterialPosition(list, {
from,
to,
});
const expectResult = generateMaterials([0, 1, 3, 2, 4, 5]);
expect(list).toStrictEqual(expectResult);
// revert action
// result [2] [3]
const revertInfo = calcRevertMovePosition({ from, to }) as { from: number[]; to: number[] };
// revert [3] -> [2]
moveMaterialPosition(list, {
from: revertInfo.from,
to: revertInfo.to,
});
expect(list).toStrictEqual(generateMaterials([0, 1, 2, 3, 4, 5]));
});
// [4] -> [2] yes
// [0, 1, 4, 2, 3, 4, 5]
// [0, 1, 4, 2, 3, 5]
test('moveMaterialPosition, move-up [4] -> [2]', () => {
const list: StrictMaterial[] = generateMaterials([0, 1, 2, 3, 4, 5]);
const from = [4];
const to = [2];
moveMaterialPosition(list, {
from,
to,
});
const expectResult = generateMaterials([0, 1, 4, 2, 3, 5]);
expect(list).toStrictEqual(expectResult);
// revert action
// result [5] [2]
const revertInfo = calcRevertMovePosition({ from, to }) as { from: number[]; to: number[] };
// revert [2] -> [5]
moveMaterialPosition(list, {
from: revertInfo.from,
to: revertInfo.to,
});
expect(list).toStrictEqual(generateMaterials([0, 1, 2, 3, 4, 5]));
});
// [3, 2, 1] -> [2]
test('moveMaterialPosition, move-up [3, 2, 1] -> [2]', () => {
const list: StrictMaterial[] = generateMaterials([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
const from = [3, 2, 1];
const to = [2];
moveMaterialPosition(list, {
from,
to,
});
const expectResult = generateMaterials([0, 1, 1, 2, [0, 1, [0, 2, 3], 3], 4, 5]);
expect(list).toStrictEqual(expectResult);
// revert action
// result from: [ 4, 2, 1 ], to: [ 2 ]
const revertInfo = calcRevertMovePosition({ from, to }) as { from: number[]; to: number[] };
// revert [2] -> [ 4, 2, 1 ]
moveMaterialPosition(list, {
from: revertInfo.from,
to: revertInfo.to,
});
expect(list).toStrictEqual(generateMaterials([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]));
});
// [1] -> [1, 2, 3]
test('moveMaterialPosition, move-up [1] -> [1, 2, 3]', () => {
const list: StrictMaterial[] = generateMaterials([0, [0, 1, [0, 1, 2, 3, 4, 5], 3, 4, 5], 2, 3, 4, 5]);
const from = [1];
const to = [1, 2, 3];
moveMaterialPosition(list, {
from,
to,
});
const expectResult = generateMaterials([0, [0, 1, [0, 1, 2, 3, 4, 5], 3, 4, 5], 2, 3, 4, 5]);
expect(list).toStrictEqual(expectResult);
// revert action null
const revertInfo = calcRevertMovePosition({ from, to });
expect(revertInfo).toBeNull();
});
// [1, 2, 3, 4, 5] -> [1, 2, 2]
test('moveMaterialPosition, move-up [1, 2, 3, 4, 5] -> [1, 2, 2]', () => {
const list: StrictMaterial[] = generateMaterials([
0,
[0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5],
2,
3,
4,
5,
]);
const from = [1, 2, 3, 4, 5];
const to = [1, 2, 2];
moveMaterialPosition(list, {
from,
to,
});
const expectResult = generateMaterials([
0,
[0, 1, [0, 1, 5, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4], 5], 4, 5], 3, 4, 5],
2,
3,
4,
5,
]);
expect(list).toStrictEqual(expectResult);
// revert action
// result from: [ 1, 2, 4, 4, 5 ], to: [ 1, 2, 2 ]
const revertInfo = calcRevertMovePosition({ from, to }) as { from: number[]; to: number[] };
// revert [ 1, 2, 2 ] -> [ 1, 2, 4, 4, 5 ]
moveMaterialPosition(list, {
from: revertInfo.from,
to: revertInfo.to,
});
expect(list).toStrictEqual(
generateMaterials([0, [0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5], 2, 3, 4, 5])
);
});
// [1] -> [1]
test('moveMaterialPosition, move-up [1] -> [1]', () => {
const list: StrictMaterial[] = generateMaterials([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
const from = [1];
const to = [1];
moveMaterialPosition(list, {
from,
to,
});
const expectResult = generateMaterials([0, 1, 2, [0, 1, [0, 1, 2, 3], 3], 4, 5]);
expect(list).toStrictEqual(expectResult);
// revert action null
const revertInfo = calcRevertMovePosition({ from, to });
expect(revertInfo).toBeNull();
});
// [2, 4] -> [1, 2]
test('moveMaterialPosition, move-up [1, 2] -> [2, 4]', () => {
const list: StrictMaterial[] = generateMaterials([0, [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], 3, 4]);
const from = [2, 4];
const to = [1, 2];
moveMaterialPosition(list, {
from,
to,
});
const expectResult = generateMaterials([0, [0, 1, 4, 2, 3, 4], [0, 1, 2, 3], 3, 4]);
expect(list).toStrictEqual(expectResult);
// revert action
// result from: [ 2, 4 ], to: [ 1, 2 ]
const revertInfo = calcRevertMovePosition({ from, to }) as { from: number[]; to: number[] };
// revert [ 1, 2 ] -> [ 2, 4 ]
moveMaterialPosition(list, {
from: revertInfo.from,
to: revertInfo.to,
});
expect(list).toStrictEqual(generateMaterials([0, [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], 3, 4]));
});
});

View file

@ -1,52 +0,0 @@
import { parseHTMLToDataURL, parseSVGToDataURL } from '../../src/view/parser';
describe('@idraw/util: lib/parser', () => {
test('parseHTMLToDataURL', async () => {
const result = await parseHTMLToDataURL(
`
<style>
.btn {
line-height: 1.5715;
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
box-shadow: 0 2px #00000004;
cursor: pointer;
user-select: none;
height: 32px;
padding: 4px 15px;
font-size: 14px;
border-radius: 2px;
color: #000000d9;
background: #fff;
border-color: #d9d9d9;
}
</style>
<button class="btn" >
<span>Button</span>
</button>
`,
{
width: 120,
height: 80
}
);
expect(result).toStrictEqual(
`data:image/svg+xml;charset=utf-8;base64,CiAgICA8c3ZnIAogICAgICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIAogICAgICB3aWR0aD0iMTIwIiAKICAgICAgaGVpZ2h0ID0gIjgwIj4KICAgICAgPGZvcmVpZ25PYmplY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSI+CiAgICAgICAgPGRpdiB4bWxucyA9ICJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIj4KICAgICAgICAgIAogICAgPHN0eWxlPgogICAgLmJ0biB7CiAgICAgIGxpbmUtaGVpZ2h0OiAxLjU3MTU7CiAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICBmb250LXdlaWdodDogNDAwOwogICAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7CiAgICAgIGJhY2tncm91bmQtaW1hZ2U6IG5vbmU7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkIHRyYW5zcGFyZW50OwogICAgICBib3gtc2hhZG93OiAwIDJweCAjMDAwMDAwMDQ7CiAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgICAgdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgIGhlaWdodDogMzJweDsKICAgICAgcGFkZGluZzogNHB4IDE1cHg7CiAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgYm9yZGVyLXJhZGl1czogMnB4OwogICAgICBjb2xvcjogIzAwMDAwMGQ5OwogICAgICBiYWNrZ3JvdW5kOiAjZmZmOwogICAgICBib3JkZXItY29sb3I6ICNkOWQ5ZDk7CiAgICB9CiAgICA8L3N0eWxlPgogICAgPGJ1dHRvbiBjbGFzcz0iYnRuIiA+CiAgICAgIDxzcGFuPkJ1dHRvbjwvc3Bhbj4KICAgIDwvYnV0dG9uPgogICAgCiAgICAgICAgPC9kaXY+CiAgICAgIDwvZm9yZWlnbk9iamVjdD4KICAgIDwvc3ZnPgogICAg`
);
});
test('parseSVGToDataURL', async () => {
const result = await parseSVGToDataURL(
`<svg t="1622524780663" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8365" width="200" height="200"><path d="M881 442.4H519.7v148.5h206.4c-8.9 48-35.9 88.6-76.6 115.8-34.4 23-78.3 36.6-129.9 36.6-99.9 0-184.4-67.5-214.6-158.2-7.6-23-12-47.6-12-72.9s4.4-49.9 12-72.9c30.3-90.6 114.8-158.1 214.7-158.1 56.3 0 106.8 19.4 146.6 57.4l110-110.1c-66.5-62-153.2-100-256.6-100-149.9 0-279.6 86-342.7 211.4-26 51.8-40.8 110.4-40.8 172.4S151 632.8 177 684.6C240.1 810 369.8 896 519.7 896c103.6 0 190.4-34.4 253.8-93 72.5-66.8 114.4-165.2 114.4-282.1 0-27.2-2.4-53.3-6.9-78.5z" p-id="8366" fill="#1296db"></path></svg>`
);
expect(result).toStrictEqual(
`data:image/svg+xml;charset=utf-8;base64,PHN2ZyB0PSIxNjIyNTI0NzgwNjYzIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjgzNjUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNODgxIDQ0Mi40SDUxOS43djE0OC41aDIwNi40Yy04LjkgNDgtMzUuOSA4OC42LTc2LjYgMTE1LjgtMzQuNCAyMy03OC4zIDM2LjYtMTI5LjkgMzYuNi05OS45IDAtMTg0LjQtNjcuNS0yMTQuNi0xNTguMi03LjYtMjMtMTItNDcuNi0xMi03Mi45czQuNC00OS45IDEyLTcyLjljMzAuMy05MC42IDExNC44LTE1OC4xIDIxNC43LTE1OC4xIDU2LjMgMCAxMDYuOCAxOS40IDE0Ni42IDU3LjRsMTEwLTExMC4xYy02Ni41LTYyLTE1My4yLTEwMC0yNTYuNi0xMDAtMTQ5LjkgMC0yNzkuNiA4Ni0zNDIuNyAyMTEuNC0yNiA1MS44LTQwLjggMTEwLjQtNDAuOCAxNzIuNFMxNTEgNjMyLjggMTc3IDY4NC42QzI0MC4xIDgxMCAzNjkuOCA4OTYgNTE5LjcgODk2YzEwMy42IDAgMTkwLjQtMzQuNCAyNTMuOC05MyA3Mi41LTY2LjggMTE0LjQtMTY1LjIgMTE0LjQtMjgyLjEgMC0yNy4yLTIuNC01My4zLTYuOS03OC41eiIgcC1pZD0iODM2NiIgZmlsbD0iIzEyOTZkYiI+PC9wYXRoPjwvc3ZnPg==`
);
});
});

View file

@ -0,0 +1,189 @@
import {
convertSVGPathToContext2DCommands,
calcSVGPathBoundingBox,
calcPathCommondsBoundingBox,
parseSVGPath,
} from '@idraw/util';
describe('convertSVGPathToContext2DCommands', () => {
beforeEach(() => {
jest.spyOn(Math, 'random').mockReturnValue(0.987654321);
});
test('convertSVGPathToContext2DCommands', () => {
const svgPath = `M 10,30 L 50,30 H 80 V 60 C 100,60 120,80 120,100 S 140,140 160,140 Q 180,140 190,150 T 210,170 A 30,30 0 0,1 240,200 Z m 10,10 l 10,0 h 10 v 10 c 0,0 10,10 10,10 s 10,10 10,10 q 0,0 10,10 t 10,10 a 5,5 0 0,1 10,10`;
const cmds = convertSVGPathToContext2DCommands(svgPath);
expect(cmds).toStrictEqual([
{ id: 'zk00000ytuzk0000', name: 'beginPath', params: null },
{ id: 'zk00000ytuzk0000', name: 'moveTo', params: { x: 10, y: 30 } },
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 23.333333333333336, cp1y: 30, cp2x: 36.66666666666667, cp2y: 30, x: 50, y: 30 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 60, cp1y: 30, cp2x: 70, cp2y: 30, x: 80, y: 30 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 80, cp1y: 40, cp2x: 80, cp2y: 50, x: 80, y: 60 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 100, cp1y: 60, cp2x: 120, cp2y: 80, x: 120, y: 100 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 120, cp1y: 120, cp2x: 140, cp2y: 140, x: 160, y: 140 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: {
cp1x: 173.33333333333334,
cp1y: 140,
cp2x: 183.33333333333334,
cp2y: 143.33333333333334,
x: 190,
y: 150,
},
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: {
cp1x: 194.44444444444443,
cp1y: 154.44444444444443,
cp2x: 201.11111111111111,
cp2y: 161.11111111111111,
x: 210,
y: 170,
},
},
{
id: 'zk00000ytuzk0000',
name: 'ellipse',
params: {
centerX: 210,
centerY: 200,
radiusX: 30,
radiusY: 30,
rotation: 0,
startRadian: -1.5707963267948966,
endRadian: 0,
anticlockwise: false,
},
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: {
cp1x: 163.33333333333331,
cp1y: 143.33333333333334,
cp2x: 86.66666666666666,
cp2y: 86.66666666666667,
x: 10,
y: 30,
},
},
{ id: 'zk00000ytuzk0000', name: 'closePath', params: null },
{ id: 'zk00000ytuzk0000', name: 'moveTo', params: { x: 20, y: 40 } },
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 23.333333333333332, cp1y: 40, cp2x: 26.666666666666668, cp2y: 40, x: 30, y: 40 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 33.333333333333336, cp1y: 40, cp2x: 36.666666666666664, cp2y: 40, x: 40, y: 40 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 40, cp1y: 43.333333333333336, cp2x: 40, cp2y: 46.666666666666664, x: 40, y: 50 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 40, cp1y: 50, cp2x: 50, cp2y: 60, x: 50, y: 60 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 50, cp1y: 60, cp2x: 60, cp2y: 70, x: 60, y: 70 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: { cp1x: 60, cp1y: 70, cp2x: 63.333333333333336, cp2y: 73.33333333333333, x: 70, y: 80 },
},
{
id: 'zk00000ytuzk0000',
name: 'bezierCurveTo',
params: {
cp1x: 74.44444444444444,
cp1y: 84.44444444444444,
cp2x: 77.77777777777777,
cp2y: 87.77777777777779,
x: 80,
y: 90,
},
},
{
id: 'zk00000ytuzk0000',
name: 'ellipse',
params: {
centerX: 85,
centerY: 95,
radiusX: 7.0710678118654755,
radiusY: 7.0710678118654755,
rotation: 0,
startRadian: -2.356194490192345,
endRadian: 0.7853981633974483,
anticlockwise: false,
},
},
]);
});
});
describe('calcSVGPathBoundingBox', () => {
test('calcSVGPathBoundingBox', () => {
const svgPath = `M 10,30 L 50,30 H 80 V 60 C 100,60 120,80 120,100 S 140,140 160,140 Q 180,140 190,150 T 210,170 A 30,30 0 0,1 240,200 Z m 10,10 l 10,0 h 10 v 10 c 0,0 10,10 10,10 s 10,10 10,10 q 0,0 10,10 t 10,10 a 5,5 0 0,1 10,10`;
const boundingBox = calcSVGPathBoundingBox(svgPath);
expect(boundingBox).toStrictEqual({
minX: 10,
minY: 30,
maxX: 240,
maxY: 200,
width: 230,
height: 170,
start: { x: 10, y: 30 },
end: { x: 240, y: 200 },
});
});
});
describe('calcPathCommondsBoundingBox', () => {
test('calcPathCommondsBoundingBox', () => {
const svgPath = `M 10,30 L 50,30 H 80 V 60 C 100,60 120,80 120,100 S 140,140 160,140 Q 180,140 190,150 T 210,170 A 30,30 0 0,1 240,200 Z m 10,10 l 10,0 h 10 v 10 c 0,0 10,10 10,10 s 10,10 10,10 q 0,0 10,10 t 10,10 a 5,5 0 0,1 10,10`;
const cmds = parseSVGPath(svgPath);
const boundingBox = calcPathCommondsBoundingBox(cmds);
expect(boundingBox).toStrictEqual({
minX: 10,
minY: 30,
maxX: 240,
maxY: 200,
width: 230,
height: 170,
start: { x: 10, y: 30 },
end: { x: 240, y: 200 },
});
});
});

View file

@ -1,292 +0,0 @@
import { createElement, resizeEffectGroupElement } from '@idraw/util';
import type { Element } from '@idraw/types';
const createGroupByRatio = (opts?: { xRatio?: number; yRatio?: number }) => {
const { xRatio = 1, yRatio = 1 } = opts || {};
const minRatio = Math.min(xRatio, yRatio);
const maxRatio = Math.max(xRatio, yRatio);
const midRatio = (minRatio + maxRatio) / 2;
const group: Element<'group'> = createElement('group', {
uuid: 'test-001',
x: 10,
y: 10,
w: 2000 * xRatio,
h: 2000 * yRatio,
detail: {
children: [
createElement('rect', { uuid: 'test-002', x: 20 * xRatio, y: 20 * yRatio, w: 20 * xRatio, h: 20 * yRatio }),
createElement('circle', { uuid: 'test-003', x: 40 * xRatio, y: 40 * yRatio, w: 40 * xRatio, h: 40 * yRatio }),
createElement('text', {
uuid: 'test-004',
x: 60 * xRatio,
y: 60 * yRatio,
w: 60 * xRatio,
h: 60 * yRatio,
detail: {
fontSize: 16 * midRatio,
// lineHeight: 32 * midRatio,
text: 'Text in Group'
}
}),
createElement('image', {
uuid: 'test-005',
x: 80 * xRatio,
y: 80 * yRatio,
w: 80 * xRatio,
h: 80 * yRatio,
detail: { src: 'https://example.com/002.png' }
}),
createElement('group', {
uuid: 'test-100',
x: 500 * xRatio,
y: 500 * yRatio,
w: 1000 * xRatio,
h: 1000 * yRatio,
detail: {
children: [
createElement('rect', {
uuid: 'test-101',
x: 20 * xRatio,
y: 20 * yRatio,
w: 20 * xRatio,
h: 20 * yRatio
}),
createElement('circle', {
uuid: 'test-102',
x: 40 * xRatio,
y: 40 * yRatio,
w: 40 * xRatio,
h: 40 * yRatio
}),
createElement('text', {
uuid: 'test-103',
x: 60 * xRatio,
y: 60 * yRatio,
w: 60 * xRatio,
h: 60 * yRatio,
detail: {
fontSize: 16 * midRatio,
text: 'Text in Group'
}
}),
createElement('image', {
uuid: 'test-104',
x: 80 * xRatio,
y: 80 * yRatio,
w: 80 * xRatio,
h: 80 * yRatio,
detail: { src: 'https://example.com/002.png' }
})
]
}
})
]
},
operations: {
resizeEffect: 'deepResize'
}
});
return group;
};
const createGroupByFixed = (opts: { moveX: number; moveY: number; moveW: number; moveH: number }) => {
const { moveX, moveY, moveW, moveH } = opts || {};
const group: Element<'group'> = createElement('group', {
uuid: 'test-001',
x: 10 + moveX,
y: 10 + moveY,
w: 2000 + moveW,
h: 2000 + moveH,
detail: {
children: [
createElement('rect', { uuid: 'test-002', x: 20 - moveX, y: 20 - moveY, w: 20, h: 20 }),
createElement('circle', { uuid: 'test-003', x: 40 - moveX, y: 40 - moveY, w: 40, h: 40 }),
createElement('text', {
uuid: 'test-004',
x: 60 - moveX,
y: 60 - moveY,
w: 60,
h: 60,
detail: {
fontSize: 16,
text: 'Text in Group'
}
}),
createElement('image', {
uuid: 'test-005',
x: 80 - moveX,
y: 80 - moveY,
w: 80,
h: 80,
detail: { src: 'https://example.com/002.png' }
}),
createElement('group', {
uuid: 'test-100',
x: 500 - moveX,
y: 500 - moveY,
w: 1000,
h: 1000,
detail: {
children: [
createElement('rect', {
uuid: 'test-101',
x: 20,
y: 20,
w: 20,
h: 20
}),
createElement('circle', {
uuid: 'test-102',
x: 40,
y: 40,
w: 40,
h: 40
}),
createElement('text', {
uuid: 'test-103',
x: 60,
y: 60,
w: 60,
h: 60,
detail: {
fontSize: 16,
text: 'Text in Group'
}
}),
createElement('image', {
uuid: 'test-104',
x: 80,
y: 80,
w: 80,
h: 80,
detail: { src: 'https://example.com/002.png' }
})
]
}
})
]
},
operations: {
resizeEffect: 'deepResize'
}
});
return group;
};
describe('resizeEffectGroupElement', () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('deepSize', () => {
const group = createGroupByRatio();
const xRatio = 2;
const yRatio = 3;
const record = resizeEffectGroupElement(
group,
{
w: group.w * xRatio,
h: group.h * yRatio
},
{
resizeEffect: 'deepResize'
}
);
expect(group).toStrictEqual(
createGroupByRatio({
xRatio,
yRatio
})
);
expect(record).toStrictEqual({
type: 'resizeElements',
time: 1735689600000,
content: {
method: 'modifyElements',
before: [
{ uuid: 'test-001', x: 10, y: 10, w: 2000, h: 2000 },
{ x: 20, y: 20, w: 20, h: 20, uuid: 'test-002' },
{ x: 40, y: 40, w: 40, h: 40, uuid: 'test-003' },
{ x: 60, y: 60, w: 60, h: 60, uuid: 'test-004', 'detail.fontSize': 16 },
{ x: 80, y: 80, w: 80, h: 80, uuid: 'test-005' },
{ x: 500, y: 500, w: 1000, h: 1000, uuid: 'test-100' },
{ x: 20, y: 20, w: 20, h: 20, uuid: 'test-101' },
{ x: 40, y: 40, w: 40, h: 40, uuid: 'test-102' },
{ x: 60, y: 60, w: 60, h: 60, uuid: 'test-103', 'detail.fontSize': 16 },
{ x: 80, y: 80, w: 80, h: 80, uuid: 'test-104' }
],
after: [
{ uuid: 'test-001', x: 10, y: 10, w: 4000, h: 6000 },
{ x: 40, y: 60, w: 40, h: 60, uuid: 'test-002' },
{ x: 80, y: 120, w: 80, h: 120, uuid: 'test-003' },
{ x: 120, y: 180, w: 120, h: 180, uuid: 'test-004', 'detail.fontSize': 40 },
{ x: 160, y: 240, w: 160, h: 240, uuid: 'test-005' },
{ x: 1000, y: 1500, w: 2000, h: 3000, uuid: 'test-100' },
{ x: 40, y: 60, w: 40, h: 60, uuid: 'test-101' },
{ x: 80, y: 120, w: 80, h: 120, uuid: 'test-102' },
{ x: 120, y: 180, w: 120, h: 180, uuid: 'test-103', 'detail.fontSize': 40 },
{ x: 160, y: 240, w: 160, h: 240, uuid: 'test-104' }
]
}
});
});
test('fixed', () => {
const group = createGroupByRatio();
const moveX = 99;
const moveY = 88;
const moveW = 77;
const moveH = 66;
const record = resizeEffectGroupElement(
group,
{
x: group.x + moveX,
y: group.y + moveY,
w: group.w + moveW,
h: group.h + moveH
},
{
resizeEffect: 'fixed'
}
);
expect(group).toStrictEqual(
createGroupByFixed({
moveX,
moveY,
moveW,
moveH
})
);
expect(record).toStrictEqual({
type: 'resizeElements',
time: 1735689600000,
content: {
method: 'modifyElements',
before: [
{ uuid: 'test-001', x: 10, y: 10, w: 2000, h: 2000 },
{ uuid: 'test-002', x: 20, y: 20 },
{ uuid: 'test-003', x: 40, y: 40 },
{ uuid: 'test-004', x: 60, y: 60 },
{ uuid: 'test-005', x: 80, y: 80 },
{ uuid: 'test-100', x: 500, y: 500 }
],
after: [
{ uuid: 'test-001', x: 109, y: 98, w: 2077, h: 2066 },
{ uuid: 'test-002', x: -79, y: -68 },
{ uuid: 'test-003', x: -59, y: -48 },
{ uuid: 'test-004', x: -39, y: -28 },
{ uuid: 'test-005', x: -19, y: -8 },
{ uuid: 'test-100', x: 401, y: 412 }
]
}
});
});
});

View file

@ -0,0 +1,289 @@
import { createMaterial, resizeEffectGroupMaterial } from '@idraw/util';
import type { StrictMaterial } from '@idraw/types';
const createGroupByRatio = (opts?: { xRatio?: number; yRatio?: number }) => {
const { xRatio = 1, yRatio = 1 } = opts || {};
const minRatio = Math.min(xRatio, yRatio);
const maxRatio = Math.max(xRatio, yRatio);
const midRatio = (minRatio + maxRatio) / 2;
const group: StrictMaterial<'group'> = createMaterial('group', {
id: 'test-001',
x: 10,
y: 10,
width: 2000 * xRatio,
height: 2000 * yRatio,
children: [
createMaterial('rect', {
id: 'test-002',
x: 20 * xRatio,
y: 20 * yRatio,
width: 20 * xRatio,
height: 20 * yRatio,
}),
createMaterial('circle', {
id: 'test-003',
x: 40 * xRatio,
y: 40 * yRatio,
width: 40 * xRatio,
height: 40 * yRatio,
}),
createMaterial('text', {
id: 'test-004',
x: 60 * xRatio,
y: 60 * yRatio,
width: 60 * xRatio,
height: 60 * yRatio,
fontSize: 16 * midRatio,
// lineHeight: 32 * midRatio,
text: 'Text in Group',
}),
createMaterial('image', {
id: 'test-005',
x: 80 * xRatio,
y: 80 * yRatio,
width: 80 * xRatio,
height: 80 * yRatio,
src: 'https://example.com/002.png',
}),
createMaterial('group', {
id: 'test-100',
x: 500 * xRatio,
y: 500 * yRatio,
width: 1000 * xRatio,
height: 1000 * yRatio,
children: [
createMaterial('rect', {
id: 'test-101',
x: 20 * xRatio,
y: 20 * yRatio,
width: 20 * xRatio,
height: 20 * yRatio,
}),
createMaterial('circle', {
id: 'test-102',
x: 40 * xRatio,
y: 40 * yRatio,
width: 40 * xRatio,
height: 40 * yRatio,
}),
createMaterial('text', {
id: 'test-103',
x: 60 * xRatio,
y: 60 * yRatio,
width: 60 * xRatio,
height: 60 * yRatio,
fontSize: 16 * midRatio,
text: 'Text in Group',
}),
createMaterial('image', {
id: 'test-104',
x: 80 * xRatio,
y: 80 * yRatio,
width: 80 * xRatio,
height: 80 * yRatio,
src: 'https://example.com/002.png',
}),
],
}),
],
operations: {
resizeEffect: 'deepResize',
},
});
return group;
};
const createGroupByFixed = (opts: { moveX: number; moveY: number; moveW: number; moveH: number }) => {
const { moveX, moveY, moveW, moveH } = opts || {};
const group: StrictMaterial<'group'> = createMaterial('group', {
id: 'test-001',
x: 10 + moveX,
y: 10 + moveY,
width: 2000 + moveW,
height: 2000 + moveH,
children: [
createMaterial('rect', { id: 'test-002', x: 20 - moveX, y: 20 - moveY, width: 20, height: 20 }),
createMaterial('circle', { id: 'test-003', x: 40 - moveX, y: 40 - moveY, width: 40, height: 40 }),
createMaterial('text', {
id: 'test-004',
x: 60 - moveX,
y: 60 - moveY,
width: 60,
height: 60,
fontSize: 16,
text: 'Text in Group',
}),
createMaterial('image', {
id: 'test-005',
x: 80 - moveX,
y: 80 - moveY,
width: 80,
height: 80,
src: 'https://example.com/002.png',
}),
createMaterial('group', {
id: 'test-100',
x: 500 - moveX,
y: 500 - moveY,
width: 1000,
height: 1000,
children: [
createMaterial('rect', {
id: 'test-101',
x: 20,
y: 20,
width: 20,
height: 20,
}),
createMaterial('circle', {
id: 'test-102',
x: 40,
y: 40,
width: 40,
height: 40,
}),
createMaterial('text', {
id: 'test-103',
x: 60,
y: 60,
width: 60,
height: 60,
fontSize: 16,
text: 'Text in Group',
}),
createMaterial('image', {
id: 'test-104',
x: 80,
y: 80,
width: 80,
height: 80,
src: 'https://example.com/002.png',
}),
],
}),
],
operations: {
resizeEffect: 'deepResize',
},
});
return group;
};
describe('resizeEffectGroupMaterial', () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2025-01-01'));
});
test('deepSize', () => {
const group = createGroupByRatio();
const xRatio = 2;
const yRatio = 3;
const record = resizeEffectGroupMaterial(
group,
{
width: group.width * xRatio,
height: group.height * yRatio,
},
{
resizeEffect: 'deepResize',
}
);
expect(group).toStrictEqual(
createGroupByRatio({
xRatio,
yRatio,
})
);
expect(record).toStrictEqual({
type: 'resizeMaterials',
time: 1735689600000,
content: {
method: 'modifyMaterials',
before: [
{ id: 'test-001', x: 10, y: 10, width: 2000, height: 2000 },
{ x: 20, y: 20, width: 20, height: 20, id: 'test-002' },
{ x: 40, y: 40, width: 40, height: 40, id: 'test-003' },
{ x: 60, y: 60, width: 60, height: 60, id: 'test-004', fontSize: 16 },
{ x: 80, y: 80, width: 80, height: 80, id: 'test-005' },
{ x: 500, y: 500, width: 1000, height: 1000, id: 'test-100' },
{ x: 20, y: 20, width: 20, height: 20, id: 'test-101' },
{ x: 40, y: 40, width: 40, height: 40, id: 'test-102' },
{ x: 60, y: 60, width: 60, height: 60, id: 'test-103', fontSize: 16 },
{ x: 80, y: 80, width: 80, height: 80, id: 'test-104' },
],
after: [
{ id: 'test-001', x: 10, y: 10, width: 4000, height: 6000 },
{ x: 40, y: 60, width: 40, height: 60, id: 'test-002' },
{ x: 80, y: 120, width: 80, height: 120, id: 'test-003' },
{ x: 120, y: 180, width: 120, height: 180, id: 'test-004', fontSize: 40 },
{ x: 160, y: 240, width: 160, height: 240, id: 'test-005' },
{ x: 1000, y: 1500, width: 2000, height: 3000, id: 'test-100' },
{ x: 40, y: 60, width: 40, height: 60, id: 'test-101' },
{ x: 80, y: 120, width: 80, height: 120, id: 'test-102' },
{ x: 120, y: 180, width: 120, height: 180, id: 'test-103', fontSize: 40 },
{ x: 160, y: 240, width: 160, height: 240, id: 'test-104' },
],
},
});
});
test('fixed', () => {
const group = createGroupByRatio();
const moveX = 99;
const moveY = 88;
const moveW = 77;
const moveH = 66;
const record = resizeEffectGroupMaterial(
group,
{
x: group.x + moveX,
y: group.y + moveY,
width: group.width + moveW,
height: group.height + moveH,
},
{
resizeEffect: 'fixed',
}
);
expect(group).toStrictEqual(
createGroupByFixed({
moveX,
moveY,
moveW,
moveH,
})
);
expect(record).toStrictEqual({
type: 'resizeMaterials',
time: 1735689600000,
content: {
method: 'modifyMaterials',
before: [
{ id: 'test-001', x: 10, y: 10, width: 2000, height: 2000 },
{ id: 'test-002', x: 20, y: 20 },
{ id: 'test-003', x: 40, y: 40 },
{ id: 'test-004', x: 60, y: 60 },
{ id: 'test-005', x: 80, y: 80 },
{ id: 'test-100', x: 500, y: 500 },
],
after: [
{ id: 'test-001', x: 109, y: 98, width: 2077, height: 2066 },
{ id: 'test-002', x: -79, y: -68 },
{ id: 'test-003', x: -59, y: -48 },
{ id: 'test-004', x: -39, y: -28 },
{ id: 'test-005', x: -19, y: -8 },
{ id: 'test-100', x: 401, y: 412 },
],
},
});
});
});

View file

@ -0,0 +1,83 @@
import { parseStyles, injectStyles } from '@idraw/util';
import type { StylesProps } from '@idraw/types';
// import { parseStyles, injectStyles, StylesProps } from './styleUtils';
describe('parseStyles', () => {
it('should convert simple style object to CSS string', () => {
const styles: StylesProps = {
color: 'red',
backgroundColor: 'blue',
};
const css = parseStyles(styles, '.test');
expect(css).toBe('.test { color: red; background-color: blue; }');
});
it('should handle nested pseudo-classes', () => {
const styles: StylesProps = {
color: 'red',
'&:hover': {
color: 'white',
},
};
const css = parseStyles(styles, '.btn');
expect(css).toContain('.btn { color: red; }');
expect(css).toContain('.btn:hover { color: white; }');
});
it('should handle child selectors', () => {
const styles: StylesProps = {
'.child': {
fontSize: '12px',
},
};
const css = parseStyles(styles, '.parent');
expect(css).toContain('.parent .child { font-size: 12px; }');
});
it('should handle media queries', () => {
const styles: StylesProps = {
'@media (max-width:600px)': {
color: 'green',
},
};
const css = parseStyles(styles, '.responsive');
expect(css).toContain('@media (max-width:600px) { .responsive { color: green; } }');
});
});
describe('injectStyles', () => {
beforeEach(() => {
// Mock adoptedStyleSheets support
(document as any).adoptedStyleSheets = [];
(global as any).CSSStyleSheet = class {
cssText = '';
replaceSync(text: string) {
this.cssText = text;
}
};
});
it('should inject style and return className', () => {
const styles: StylesProps = {
color: 'red',
backgroundColor: 'blue',
};
injectStyles({ styles, rootClassName: 'btn' });
// Ensure adoptedStyleSheets has one item
expect((document as any).adoptedStyleSheets.length).toBe(1);
// Check CSS text content
const sheet = (document as any).adoptedStyleSheets[0];
expect(sheet.cssText).toContain('.btn { color: red; background-color: blue; }');
});
it('should append multiple sheets without overwriting', () => {
injectStyles({ styles: { color: 'red' }, rootClassName: 'c1' });
injectStyles({ styles: { color: 'blue' }, rootClassName: 'c2' });
expect((document as any).adoptedStyleSheets.length).toBe(2);
});
});

View file

@ -1,61 +0,0 @@
import { delay, throttle, compose } from '@idraw/util';
describe('@idraw/util: lib/delay', () => {
test('delay', async () => {
const start = Date.now();
const time = 1000;
await delay(time);
const count = Date.now() - start;
expect(count >= time).toStrictEqual(true);
});
test('throttle', async () => {
let count = 0;
function doThrottle() {
return new Promise((resolve) => {
const func = throttle(function () {
count++;
}, 100);
let interval = setInterval(() => {
if (count >= 5) {
clearInterval(interval);
resolve(null);
}
func();
}, 5);
});
}
await doThrottle();
expect(count).toStrictEqual(5);
});
test('compose', async () => {
let middleware = [];
let context = {
data: []
};
middleware.push(async (ctx: any, next: any) => {
ctx.data.push(1);
await next();
ctx.data.push(6);
});
middleware.push(async (ctx: any, next: any) => {
ctx.data.push(2);
await next();
ctx.data.push(5);
});
middleware.push(async (ctx: any, next: any) => {
ctx.data.push(3);
await next();
ctx.data.push(4);
});
const fn = compose(middleware);
await fn(context);
expect(context).toStrictEqual({ data: [1, 2, 3, 4, 5, 6] });
});
});