diff --git a/README.md b/README.md index f3e9cd22..665c94bf 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,22 @@ const sampleCircuit = { 2. Draw it in a `div`: ```js const sampleDiv = document.getElementById('sample'); -qviz.draw(sampleCircuit, sampleDiv, qviz.STYLES['Default']); +qviz.create(sampleCircuit) + .draw(sampleDiv) ``` Refer to the [`example`](./example) folder for an example on how to use quantum-viz.js. Notice that in order to open the contents of this folder in a browser you will need first to install from source (see [below](#running-from-source)). +3. Enable extensions by: +```js +const sampleDiv = document.getElementById('sample'); +qviz.create(sampleCircuit) + .useDraggble() // drag and drop + .usePanel() // edit and add panel + .useOnCircuitChange(circuit => console.log(circuit)) // trigger when circuit changes + .draw(sampleDiv) +``` + ## Python usage To use this package with Python, use [quantum-viz](/quantum-viz). diff --git a/__tests__/__snapshots__/editable.test.ts.snap b/__tests__/__snapshots__/draggable.test.ts.snap similarity index 100% rename from __tests__/__snapshots__/editable.test.ts.snap rename to __tests__/__snapshots__/draggable.test.ts.snap diff --git a/__tests__/__snapshots__/panel.test.ts.snap b/__tests__/__snapshots__/panel.test.ts.snap new file mode 100644 index 00000000..e2589485 --- /dev/null +++ b/__tests__/__snapshots__/panel.test.ts.snap @@ -0,0 +1,996 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test addPanel Should return default addPanel 1`] = ` +
+

+ ADD +

+ + + + + + + RX + + + + + + + + + + RY + + + + + + + + + + RZ + + + + + + + + + + H + + + + + + + + + + + + + + + + + S + + + + + + + + + + T + + + + + + + + + + Y + + + + + + + + + + Z + + + + + + + + + + ZZ + + + + + +
+`; + +exports[`Test addPanel Should return default addPanel with displaySize 2 1`] = ` +
+

+ ADD +

+ + + + + + + RX + + + + + + + + + + RY + + + + + +
+`; + +exports[`Test checkbox Should return checkbox q0 1`] = ` + +`; + +exports[`Test checkbox Should return checkbox q1 1`] = ` + +`; + +exports[`Test checkbox Should return checkbox q2 1`] = ` + +`; + +exports[`Test checkboxes Test with gate X with 1 control 1`] = ` +
+ + + +
+`; + +exports[`Test checkboxes Test with gate X with 2 controls 1`] = ` +
+ + + + +
+`; + +exports[`Test children Add 0 with class to parent 1`] = `
`; + +exports[`Test children Add 1 child to parent 1`] = ` +
+

+

+`; + +exports[`Test children Add 2 children to parent 1`] = ` +
+

+

+
+`; + +exports[`Test childrenSvg Add 0 child to parent 1`] = ``; + +exports[`Test childrenSvg Add 1 child to parent 1`] = ` + + + +`; + +exports[`Test childrenSvg Add 2 children to parent 1`] = ` + + + + +`; + +exports[`Test editPanel Should return editPanel editing X gate 1`] = ` +
+

+ EDIT +

+
+ + +
+
+ + + +
+
+`; + +exports[`Test elem Should return
1`] = `
`; + +exports[`Test elem Should return
with className "panel" 1`] = ` +
+`; + +exports[`Test elem Should return

1`] = `

`; + +exports[`Test gate Should return gate H 1`] = ` + + + + + + H + + + + +`; + +exports[`Test gate Should return gate RX 1`] = ` + + + + + + RX + + + + +`; + +exports[`Test gate Should return gate X 1`] = ` + + + + + + + +`; + +exports[`Test option Should return option q0 1`] = ` + +`; + +exports[`Test option Should return option q1 1`] = ` + +`; + +exports[`Test option Should return option q2 1`] = ` + +`; + +exports[`Test panel Should return panel with addPanel visible 1`] = ` +

+
+

+ ADD +

+ + + + + + + RX + + + + + + + + + + RY + + + + + + + + + + RZ + + + + + + + + + + H + + + + + + + + + + + + + + + + + S + + + + + + + + + + T + + + + + + + + + + Y + + + + + + + + + + Z + + + + + + + + + + ZZ + + + + + +
+
+`; + +exports[`Test panel Should return panel with editPanel visible 1`] = ` +
+
+

+ EDIT +

+
+ + +
+
+ + + +
+
+
+`; + +exports[`Test text Should return gate H without display-args 1`] = ` +
+ + +
+`; + +exports[`Test text Should return gate RX with display-args 1`] = ` +
+ + +
+`; + +exports[`Test title Should return title "ADD" 1`] = ` +

+ ADD +

+`; + +exports[`Test title Should return title "EDIT" 1`] = ` +

+ EDIT +

+`; + +exports[`Test title Should return title "PANEL" 1`] = ` +

+ PANEL +

+`; + +exports[`Test toMetadata Should return metadata of gate H 1`] = ` +Object { + "controlsY": Array [], + "label": "H", + "targetsY": Array [ + Array [ + 21, + ], + ], + "type": 4, + "width": 40, + "x": 21, +} +`; + +exports[`Test toMetadata Should return metadata of gate RX 1`] = ` +Object { + "controlsY": Array [], + "label": "H", + "targetsY": Array [ + Array [ + 21, + ], + ], + "type": 4, + "width": 40, + "x": 21, +} +`; + +exports[`Test toMetadata Should return metadata of gate X 1`] = ` +Object { + "controlsY": Array [], + "label": "X", + "targetsY": Array [ + 21, + ], + "type": 3, + "width": 40, + "x": 21, +} +`; diff --git a/__tests__/editable.test.ts b/__tests__/draggable.test.ts similarity index 98% rename from __tests__/editable.test.ts rename to __tests__/draggable.test.ts index 0fde4705..a923d8cb 100644 --- a/__tests__/editable.test.ts +++ b/__tests__/draggable.test.ts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { exportedForTesting } from '../src/editable'; -import { Circuit, draw, Operation, STYLES } from '../src/index'; +import { exportedForTesting } from '../src/draggable'; +import { Circuit, create, Operation } from '../src/index'; const { _wireYs, @@ -49,7 +49,7 @@ describe('Test _hostElems', () => { }, ], }; - draw(circuit, container, STYLES['default']); + create(circuit).draw(container); }); test('should return 4 elements', () => { expect(_hostElems(container)).toMatchSnapshot(); @@ -142,7 +142,7 @@ describe('Test _wireData', () => { }, ], }; - draw(circuit, container, STYLES['default']); + create(circuit).draw(container); expect(_wireData(container)).toStrictEqual([40, 100]); }); test('3 wires should return [40,100, 180]', () => { @@ -168,7 +168,7 @@ describe('Test _wireData', () => { }, ], }; - draw(circuit, container, STYLES['default']); + create(circuit).draw(container); expect(_wireData(container)).toStrictEqual([40, 100, 180]); }); }); @@ -198,7 +198,7 @@ describe('Test _equivGateElem', () => { }, ], }; - draw(circuit, container, STYLES['default']); + create(circuit).draw(container); }); test('should return gate H', () => { const elem = container.querySelector('[class^="gate-"]') as SVGElement; diff --git a/__tests__/panel.test.ts b/__tests__/panel.test.ts new file mode 100644 index 00000000..428ede80 --- /dev/null +++ b/__tests__/panel.test.ts @@ -0,0 +1,365 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import range from 'lodash/range'; +import { Action, Dispatch, exportedForTesting, PanelContext, PanelOptions } from '../src/panel'; + +const { + panel, + addPanel, + editPanel, + elem, + children, + childrenSvg, + title, + select, + option, + checkboxes, + checkbox, + text, + toMetadata, + gate, + defaultGateDictionary, +} = exportedForTesting; + +describe('Test elem', () => { + test('Should return
', () => { + expect(elem('div')).toMatchSnapshot(); + }); + test('Should return

', () => { + expect(elem('p')).toMatchSnapshot(); + }); + test('Should return

with className "panel"', () => { + expect(elem('div', 'panel')).toMatchSnapshot(); + }); +}); + +describe('Test children', () => { + let parentElem: HTMLDivElement; + beforeEach(() => { + parentElem = document.createElement('div'); + }); + test('Add 1 child to parent', () => { + const childElems = [document.createElement('p')] as HTMLElement[]; + expect(children(parentElem, childElems)).toMatchSnapshot(); + }); + test('Add 2 children to parent', () => { + const childElems = [document.createElement('p'), document.createElement('div')] as HTMLElement[]; + expect(children(parentElem, childElems)).toMatchSnapshot(); + }); + test('Add 0 with class to parent', () => { + const childElems = [] as HTMLElement[]; + expect(children(parentElem, childElems)).toMatchSnapshot(); + }); +}); + +describe('Test childrenSvg', () => { + let parentElem: SVGElement; + beforeEach(() => { + parentElem = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + }); + test('Add 0 child to parent', () => { + const childElems = [] as SVGElement[]; + expect(childrenSvg(parentElem, childElems)).toMatchSnapshot(); + }); + test('Add 1 child to parent', () => { + const childElems = [document.createElementNS('http://www.w3.org/2000/svg', 'rect')] as SVGElement[]; + expect(childrenSvg(parentElem, childElems)).toMatchSnapshot(); + }); + test('Add 2 children to parent', () => { + const childElems = [ + document.createElementNS('http://www.w3.org/2000/svg', 'rect'), // + document.createElementNS('http://www.w3.org/2000/svg', 'circle'), // + ] as SVGElement[]; + expect(childrenSvg(parentElem, childElems)).toMatchSnapshot(); + }); +}); + +describe('Test title', () => { + test('Should return title "PANEL"', () => { + expect(title('PANEL')).toMatchSnapshot(); + }); + test('Should return title "ADD"', () => { + expect(title('ADD')).toMatchSnapshot(); + }); + test('Should return title "EDIT"', () => { + expect(title('EDIT')).toMatchSnapshot(); + }); +}); + +describe('Test text', () => { + const emptyDispatch: Dispatch = (action: Action) => { + action; + }; + test('Should return gate H without display-args', () => { + const operation = { + gate: 'H', + targets: [{ qId: 0 }], + }; + expect(text('Display', 'display-input', emptyDispatch, operation)).toMatchSnapshot(); + }); + test('Should return gate RX with display-args', () => { + const operation = { + gate: 'RX', + displayArgs: '(0.25)', + isControlled: true, + controls: [{ qId: 1 }], + targets: [{ qId: 0 }], + }; + expect(text('Display', 'display-input', emptyDispatch, operation)).toMatchSnapshot(); + }); +}); + +describe('Test toMetadata', () => { + test('Should return metadata of gate H', () => { + const operation = { + gate: 'H', + targets: [{ qId: 0 }], + }; + expect(toMetadata(operation, 0, 0)).toMatchSnapshot(); + }); + test('Should return metadata of gate RX', () => { + const operation = { + gate: 'H', + targets: [{ qId: 0 }], + }; + expect(toMetadata(operation, 0, 0)).toMatchSnapshot(); + }); + test('Should return metadata of gate X', () => { + const operation = { + gate: 'X', + targets: [{ qId: 3 }], + }; + expect(toMetadata(operation, 0, 0)).toMatchSnapshot(); + }); +}); + +describe('Test select', () => { + const emptyDispatch: Dispatch = (action: Action) => { + action; + }; + test('Test with gate X', () => { + const operation = { + gate: 'X', + targets: [{ qId: 0 }], + controls: [{ qId: 1 }], + }; + const registerSize = 2; + const options = range(registerSize).map((i) => ({ value: `${i}`, text: `q${i}` })); + const target = operation?.targets[0].qId; + select('Target', 'target-input', options, target || 0, emptyDispatch, operation); + }); + test('Test with gate H', () => { + const operation = { + gate: 'H', + targets: [{ qId: 0 }], + }; + const registerSize = 2; + const options = range(registerSize).map((i) => ({ value: `${i}`, text: `q${i}` })); + const target = operation?.targets[0].qId; + select('Target', 'target-input', options, target || 0, emptyDispatch, operation); + }); +}); + +describe('Test option', () => { + test('Should return option q0', () => { + expect(option('0', 'q0')).toMatchSnapshot(); + }); + test('Should return option q1', () => { + expect(option('1', 'q1')).toMatchSnapshot(); + }); + test('Should return option q2', () => { + expect(option('2', 'q2')).toMatchSnapshot(); + }); +}); + +describe('Test checkboxes', () => { + const emptyDispatch: Dispatch = (action: Action) => { + action; + }; + test('Test with gate X with 1 control', () => { + const operation = { + gate: 'X', + targets: [{ qId: 0 }], + controls: [{ qId: 1 }], + }; + const registerSize = 2; + const options = range(registerSize).map((i) => ({ value: `${i}`, text: `q${i}` })); + const controls = operation.controls?.map((control) => control.qId); + expect( + checkboxes('Controls', 'controls-input', options, controls || [], emptyDispatch, operation), + ).toMatchSnapshot(); + }); + test('Test with gate X with 2 controls', () => { + const operation = { + gate: 'X', + targets: [{ qId: 1 }], + controls: [{ qId: 0 }, { qId: 2 }], + }; + const registerSize = 3; + const options = range(registerSize).map((i) => ({ value: `${i}`, text: `q${i}` })); + const controls = operation.controls?.map((control) => control.qId); + expect( + checkboxes('Controls', 'controls-input', options, controls || [], emptyDispatch, operation), + ).toMatchSnapshot(); + }); +}); + +describe('Test checkbox', () => { + test('Should return checkbox q0', () => { + expect(checkbox('0', 'q0')).toMatchSnapshot(); + }); + test('Should return checkbox q1', () => { + expect(checkbox('1', 'q1')).toMatchSnapshot(); + }); + test('Should return checkbox q2', () => { + expect(checkbox('2', 'q2')).toMatchSnapshot(); + }); +}); + +describe('Test gate', () => { + const emptyDispatch: Dispatch = (action: Action) => { + action; + }; + test('Should return gate H', () => { + expect(gate(emptyDispatch, defaultGateDictionary, 'H', 0, 0)).toMatchSnapshot(); + }); + test('Should return gate X', () => { + expect(gate(emptyDispatch, defaultGateDictionary, 'X', 0, 0)).toMatchSnapshot(); + }); + test('Should return gate RX', () => { + expect(gate(emptyDispatch, defaultGateDictionary, 'RX', 0, 0)).toMatchSnapshot(); + }); +}); + +describe('Test defaulGateDictionary', () => { + test('Verify defaultGateDictionary', () => { + const expected = { + RX: { + gate: 'RX', + targets: [{ qId: 0 }], + }, + RY: { + gate: 'RY', + targets: [{ qId: 0 }], + }, + RZ: { + gate: 'RZ', + targets: [{ qId: 0 }], + }, + H: { + gate: 'H', + targets: [{ qId: 0 }], + }, + X: { + gate: 'X', + targets: [{ qId: 0 }], + }, + S: { + gate: 'S', + targets: [{ qId: 0 }], + }, + T: { + gate: 'T', + targets: [{ qId: 0 }], + }, + Y: { + gate: 'Y', + targets: [{ qId: 0 }], + }, + Z: { + gate: 'Z', + targets: [{ qId: 0 }], + }, + ZZ: { + gate: 'ZZ', + targets: [{ qId: 0 }], + }, + }; + expect(defaultGateDictionary).toMatchObject(expected); + }); +}); + +describe('Test editPanel', () => { + const emptyDispatch: Dispatch = (action: Action) => { + action; + }; + test('Should return editPanel editing X gate', () => { + const context: PanelContext = { + addMode: false, + operations: [], + operation: { + gate: 'X', + targets: [{ qId: 0 }], + }, + registerSize: 2, + container: undefined, + }; + expect(editPanel(emptyDispatch, context)).toMatchSnapshot(); + }); +}); + +describe('Test addPanel', () => { + const emptyDispatch: Dispatch = (action: Action) => { + action; + }; + test('Should return default addPanel', () => { + const context: PanelContext = { + addMode: true, + operations: [], + operation: { + gate: 'X', + targets: [{ qId: 0 }], + }, + registerSize: 2, + container: undefined, + }; + expect(addPanel(emptyDispatch, context)).toMatchSnapshot(); + }); + test('Should return default addPanel with displaySize 2', () => { + const context: PanelContext = { + addMode: true, + operations: [], + operation: { + gate: 'X', + targets: [{ qId: 0 }], + }, + registerSize: 2, + container: undefined, + }; + const options: PanelOptions = { displaySize: 2 }; + expect(addPanel(emptyDispatch, context, options)).toMatchSnapshot(); + }); +}); + +describe('Test panel', () => { + const emptyDispatch: Dispatch = (action: Action) => { + action; + }; + test('Should return panel with addPanel visible', () => { + const context: PanelContext = { + addMode: true, + operations: [], + operation: { + gate: 'X', + targets: [{ qId: 0 }], + }, + registerSize: 2, + container: undefined, + }; + expect(panel(emptyDispatch, context)).toMatchSnapshot(); + }); + test('Should return panel with editPanel visible', () => { + const context: PanelContext = { + addMode: false, + operations: [], + operation: { + gate: 'X', + targets: [{ qId: 0 }], + }, + registerSize: 2, + container: undefined, + }; + expect(panel(emptyDispatch, context)).toMatchSnapshot(); + }); +}); diff --git a/example/index.html b/example/index.html index e612d7b8..95bdfd82 100644 --- a/example/index.html +++ b/example/index.html @@ -11,6 +11,7 @@