@opentrons/shared-data 0.1.2-alpha.0 → 0.1.2-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/__tests__/__snapshots__/pipettes.test.ts.snap +5969 -0
- package/js/__tests__/deckSchemas.test.ts +87 -0
- package/js/__tests__/errors.test.js +21 -0
- package/js/__tests__/getAreSlotsAdjacent.test.ts +128 -0
- package/js/__tests__/getWellNamePerMultiTip.test.ts +162 -0
- package/js/__tests__/labwareDefQuirks.test.ts +38 -0
- package/js/__tests__/labwareDefSchemaV1.test.ts +82 -0
- package/js/__tests__/labwareDefSchemaV2.test.ts +298 -0
- package/js/__tests__/moduleAccessors.test.ts +50 -0
- package/js/__tests__/moduleSpecsSchema.test.ts +77 -0
- package/js/__tests__/pipetteSchemaV2.test.ts +107 -0
- package/js/__tests__/pipetteSpecSchemas.test.ts +48 -0
- package/js/__tests__/pipettes.test.ts +58 -0
- package/js/__tests__/protocolSchemaV4.test.ts +137 -0
- package/js/__tests__/protocolSchemaV5.test.ts +137 -0
- package/js/__tests__/protocolSchemaV6.test.ts +132 -0
- package/js/__tests__/protocolSchemaV7.test.ts +64 -0
- package/js/__tests__/protocolValidation.test.ts +83 -0
- package/js/__tests__/sortWells.test.ts +40 -0
- package/js/__tests__/splitWellsOnColumn.test.ts +21 -0
- package/js/__tests__/validateErrors.test.js +34 -0
- package/js/constants.ts +405 -0
- package/js/cypressUtils.ts +35 -0
- package/js/errors.ts +33 -0
- package/js/fixtures.ts +156 -0
- package/js/getLabware.ts +145 -0
- package/js/gripper.ts +29 -0
- package/js/helpers/__tests__/getAdapterName.test.ts +31 -0
- package/js/helpers/__tests__/getDeckDefFromLoadedLabware.test.ts +12 -0
- package/js/helpers/__tests__/getSimplestFlexDeckConfig.test.ts +176 -0
- package/js/helpers/__tests__/getVectorDifference.test.ts +25 -0
- package/js/helpers/__tests__/getVectorSum.test.ts +21 -0
- package/js/helpers/__tests__/labwareInference.test.ts +84 -0
- package/js/helpers/__tests__/orderWells.test.ts +87 -0
- package/js/helpers/__tests__/parseProtocolData.test.ts +165 -0
- package/js/helpers/__tests__/volume.test.ts +108 -0
- package/js/helpers/__tests__/wellSets.test.ts +352 -0
- package/js/helpers/get96Channel384WellPlateWells.ts +44 -0
- package/js/helpers/getAddressableAreasInProtocol.ts +98 -0
- package/js/helpers/getLoadedLabwareDefinitionsByUri.ts +30 -0
- package/js/helpers/getModuleVizDims.ts +118 -0
- package/js/helpers/getOccludedSlotCountForModule.ts +13 -0
- package/js/helpers/getSimplestFlexDeckConfig.ts +183 -0
- package/js/helpers/getVectorDifference.ts +12 -0
- package/js/helpers/getVectorSum.ts +12 -0
- package/js/helpers/getWellNamePerMultiTip.ts +86 -0
- package/js/helpers/getWellTotalVolume.ts +16 -0
- package/js/helpers/index.ts +348 -0
- package/js/helpers/labwareInference.ts +92 -0
- package/js/helpers/orderWells.ts +69 -0
- package/js/helpers/parseProtocolData.ts +141 -0
- package/js/helpers/volume.ts +35 -0
- package/js/helpers/wellIsRect.ts +6 -0
- package/js/helpers/wellSets.ts +155 -0
- package/js/index.ts +14 -0
- package/js/labwareTools/__tests__/__snapshots__/createIrregularLabware.test.ts.snap +3 -0
- package/js/labwareTools/__tests__/__snapshots__/createLabware.test.ts.snap +3 -0
- package/js/labwareTools/__tests__/createDefaultDisplayName.test.ts +111 -0
- package/js/labwareTools/__tests__/createIrregularLabware.test.ts +226 -0
- package/js/labwareTools/__tests__/createLabware.test.ts +147 -0
- package/js/labwareTools/index.ts +452 -0
- package/js/modules.ts +97 -0
- package/js/pipettes.ts +89 -0
- package/js/protocols.ts +290 -0
- package/js/schema.ts +80 -0
- package/js/scripts/generateDeckLayersFromSVG.js +46 -0
- package/js/titleCase.ts +30 -0
- package/js/types.ts +561 -0
- package/lib/js/__tests__/deckSchemas.test.d.ts +1 -0
- package/lib/js/__tests__/getAreSlotsAdjacent.test.d.ts +1 -0
- package/lib/js/__tests__/getWellNamePerMultiTip.test.d.ts +1 -0
- package/lib/js/__tests__/labwareDefQuirks.test.d.ts +1 -0
- package/lib/js/__tests__/labwareDefSchemaV1.test.d.ts +1 -0
- package/lib/js/__tests__/labwareDefSchemaV2.test.d.ts +1 -0
- package/lib/js/__tests__/moduleAccessors.test.d.ts +1 -0
- package/lib/js/__tests__/moduleSpecsSchema.test.d.ts +1 -0
- package/lib/js/__tests__/pipetteSchemaV2.test.d.ts +1 -0
- package/lib/js/__tests__/pipetteSpecSchemas.test.d.ts +1 -0
- package/lib/js/__tests__/pipettes.test.d.ts +1 -0
- package/lib/js/__tests__/protocolSchemaV4.test.d.ts +1 -0
- package/lib/js/__tests__/protocolSchemaV5.test.d.ts +1 -0
- package/lib/js/__tests__/protocolSchemaV6.test.d.ts +1 -0
- package/lib/js/__tests__/protocolSchemaV7.test.d.ts +1 -0
- package/lib/js/__tests__/protocolValidation.test.d.ts +1 -0
- package/lib/js/__tests__/sortWells.test.d.ts +1 -0
- package/lib/js/__tests__/splitWellsOnColumn.test.d.ts +1 -0
- package/lib/js/constants.d.ts +156 -0
- package/lib/js/cypressUtils.d.ts +1 -0
- package/lib/js/errors.d.ts +13 -0
- package/lib/js/fixtures.d.ts +13 -0
- package/lib/js/getLabware.d.ts +8 -0
- package/lib/js/gripper.d.ts +3 -0
- package/lib/js/helpers/__tests__/getAdapterName.test.d.ts +1 -0
- package/lib/js/helpers/__tests__/getDeckDefFromLoadedLabware.test.d.ts +1 -0
- package/lib/js/helpers/__tests__/getSimplestFlexDeckConfig.test.d.ts +1 -0
- package/lib/js/helpers/__tests__/getVectorDifference.test.d.ts +1 -0
- package/lib/js/helpers/__tests__/getVectorSum.test.d.ts +1 -0
- package/lib/js/helpers/__tests__/labwareInference.test.d.ts +1 -0
- package/lib/js/helpers/__tests__/orderWells.test.d.ts +1 -0
- package/lib/js/helpers/__tests__/parseProtocolData.test.d.ts +1 -0
- package/lib/js/helpers/__tests__/volume.test.d.ts +1 -0
- package/lib/js/helpers/__tests__/wellSets.test.d.ts +1 -0
- package/lib/js/helpers/get96Channel384WellPlateWells.d.ts +1 -0
- package/lib/js/helpers/getAddressableAreasInProtocol.d.ts +4 -0
- package/lib/js/helpers/getLoadedLabwareDefinitionsByUri.d.ts +5 -0
- package/lib/js/helpers/getModuleVizDims.d.ts +16 -0
- package/lib/js/helpers/getOccludedSlotCountForModule.d.ts +2 -0
- package/lib/js/helpers/getSimplestFlexDeckConfig.d.ts +14 -0
- package/lib/js/helpers/getVectorDifference.d.ts +2 -0
- package/lib/js/helpers/getVectorSum.d.ts +2 -0
- package/lib/js/helpers/getWellNamePerMultiTip.d.ts +3 -0
- package/lib/js/helpers/getWellTotalVolume.d.ts +2 -0
- package/lib/js/helpers/index.d.ts +44 -0
- package/lib/js/helpers/labwareInference.d.ts +16 -0
- package/lib/js/helpers/orderWells.d.ts +2 -0
- package/lib/js/helpers/parseProtocolData.d.ts +23 -0
- package/lib/js/helpers/volume.d.ts +4 -0
- package/lib/js/helpers/wellIsRect.d.ts +3 -0
- package/lib/js/helpers/wellSets.d.ts +9 -0
- package/lib/js/index.d.ts +14 -0
- package/lib/js/labwareTools/__tests__/createDefaultDisplayName.test.d.ts +1 -0
- package/lib/js/labwareTools/__tests__/createIrregularLabware.test.d.ts +1 -0
- package/lib/js/labwareTools/__tests__/createLabware.test.d.ts +1 -0
- package/lib/js/labwareTools/index.d.ts +65 -0
- package/lib/js/modules.d.ts +6 -0
- package/lib/js/pipettes.d.ts +13 -0
- package/lib/js/protocols.d.ts +3 -0
- package/lib/js/schema.d.ts +90 -0
- package/lib/js/titleCase.d.ts +1 -0
- package/lib/js/types.d.ts +398 -0
- package/package.json +1 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// json protocol file validator tests
|
|
2
|
+
import fixtureV1JsonProtocol from '../../../protocol/fixtures/1/simple.json'
|
|
3
|
+
import fixtureV3JsonProtocol from '../../../protocol/fixtures/3/simple.json'
|
|
4
|
+
import fixtureV4JsonProtocol from '../../../protocol/fixtures/4/simpleV4.json'
|
|
5
|
+
import fixtureV5JsonProtocol from '../../../protocol/fixtures/5/simpleV5.json'
|
|
6
|
+
import {
|
|
7
|
+
fileExtensionIsPython,
|
|
8
|
+
fileExtensionIsJson,
|
|
9
|
+
fileExtensionIsZip,
|
|
10
|
+
validateJsonProtocolFileContents,
|
|
11
|
+
parseProtocolData,
|
|
12
|
+
} from '../parseProtocolData'
|
|
13
|
+
|
|
14
|
+
describe('validateJsonProtocolFileContents', () => {
|
|
15
|
+
let handleError: jest.MockedFunction<any>
|
|
16
|
+
// beforeAll
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
handleError = jest.fn()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
jest.resetAllMocks()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it(`should validate schemaV3 JSON protocol`, () => {
|
|
27
|
+
const data = validateJsonProtocolFileContents(
|
|
28
|
+
JSON.stringify(fixtureV3JsonProtocol)
|
|
29
|
+
)
|
|
30
|
+
expect(data).toStrictEqual(fixtureV3JsonProtocol)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it(`should validate schemaV4 JSON protocol`, () => {
|
|
34
|
+
const data = validateJsonProtocolFileContents(
|
|
35
|
+
JSON.stringify(fixtureV4JsonProtocol)
|
|
36
|
+
)
|
|
37
|
+
expect(data).toStrictEqual(fixtureV4JsonProtocol)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it(`should validate schemaV5 JSON protocol`, () => {
|
|
41
|
+
const data = validateJsonProtocolFileContents(
|
|
42
|
+
JSON.stringify(fixtureV5JsonProtocol)
|
|
43
|
+
)
|
|
44
|
+
expect(data).toStrictEqual(fixtureV5JsonProtocol)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should call handleError with INVALID_FILE_TYPE if empty json', () => {
|
|
48
|
+
validateJsonProtocolFileContents('[]', handleError)
|
|
49
|
+
expect(handleError).toBeCalledWith('INVALID_JSON_FILE', {
|
|
50
|
+
rawError: expect.any(Error),
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('should call handleError with INVALID_JSON_FILE if json does not validate against schema', () => {
|
|
55
|
+
validateJsonProtocolFileContents(
|
|
56
|
+
JSON.stringify(fixtureV1JsonProtocol),
|
|
57
|
+
handleError
|
|
58
|
+
)
|
|
59
|
+
expect(handleError).toBeCalledWith('INVALID_JSON_FILE', {
|
|
60
|
+
schemaErrors: expect.any(Array),
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should call handleError with INVALID_JSON_FILE if json is not parseable', () => {
|
|
65
|
+
const parseSpy = jest.spyOn(JSON, 'parse')
|
|
66
|
+
parseSpy.mockImplementation(() => {
|
|
67
|
+
throw new Error('not parseable as JSON')
|
|
68
|
+
})
|
|
69
|
+
validateJsonProtocolFileContents('[]', handleError)
|
|
70
|
+
expect(handleError).toBeCalledWith('INVALID_JSON_FILE', {
|
|
71
|
+
rawError: expect.any(Error),
|
|
72
|
+
})
|
|
73
|
+
parseSpy.mockRestore()
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('file extension validators', () => {
|
|
78
|
+
describe('fileExtensionIsPython', () => {
|
|
79
|
+
it(`should return true if file name ends in .py`, () => {
|
|
80
|
+
expect(fileExtensionIsPython('foo.py')).toBe(true)
|
|
81
|
+
})
|
|
82
|
+
it(`should return false if file name ends in anything other than .py`, () => {
|
|
83
|
+
expect(fileExtensionIsPython('foo.json')).toBe(false)
|
|
84
|
+
expect(fileExtensionIsPython('foo.zip')).toBe(false)
|
|
85
|
+
expect(fileExtensionIsPython('foo.pie')).toBe(false)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
describe('fileExtensionIsJson', () => {
|
|
89
|
+
it(`should return true if file name ends in .json`, () => {
|
|
90
|
+
expect(fileExtensionIsJson('foo.json')).toBe(true)
|
|
91
|
+
})
|
|
92
|
+
it(`should return false if file name ends in anything other than .json`, () => {
|
|
93
|
+
expect(fileExtensionIsJson('foo.py')).toBe(false)
|
|
94
|
+
expect(fileExtensionIsJson('foo.zip')).toBe(false)
|
|
95
|
+
expect(fileExtensionIsJson('foo.pie')).toBe(false)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
describe('fileExtensionIsZip', () => {
|
|
99
|
+
it(`should return true if file name ends in .zip`, () => {
|
|
100
|
+
expect(fileExtensionIsZip('foo.zip')).toBe(true)
|
|
101
|
+
})
|
|
102
|
+
it(`should return false if file name ends in anything other than .zip`, () => {
|
|
103
|
+
expect(fileExtensionIsZip('foo.py')).toBe(false)
|
|
104
|
+
expect(fileExtensionIsZip('foo.json')).toBe(false)
|
|
105
|
+
expect(fileExtensionIsZip('foo.pie')).toBe(false)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
describe('fileIsBinary', () => {
|
|
109
|
+
it(`should return true if file name ends in .zip`, () => {
|
|
110
|
+
expect(fileExtensionIsZip('foo.zip')).toBe(true)
|
|
111
|
+
})
|
|
112
|
+
it(`should return false if file name ends in anything other than .zip`, () => {
|
|
113
|
+
expect(fileExtensionIsZip('foo.py')).toBe(false)
|
|
114
|
+
expect(fileExtensionIsZip('foo.json')).toBe(false)
|
|
115
|
+
expect(fileExtensionIsZip('foo.pie')).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('parseProtocolData', () => {
|
|
121
|
+
let handleError: jest.MockedFunction<any>
|
|
122
|
+
beforeEach(() => {
|
|
123
|
+
handleError = jest.fn()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
afterEach(() => {
|
|
127
|
+
jest.resetAllMocks()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it(`should return null if not JSON and metadata not given`, () => {
|
|
131
|
+
const contents = 'from math import sqrt'
|
|
132
|
+
const pythonFile = new File([contents], 'foo.py')
|
|
133
|
+
expect(parseProtocolData(pythonFile, contents)).toBe(null)
|
|
134
|
+
})
|
|
135
|
+
it(`should return metadata if not JSON and metadata is given`, () => {
|
|
136
|
+
const contents = 'from math import sqrt'
|
|
137
|
+
const pythonFile = new File([contents], 'foo.py')
|
|
138
|
+
const metadata = {
|
|
139
|
+
'protocol-name': 'Foo Extraction',
|
|
140
|
+
author: 'Dr. Bar Baz',
|
|
141
|
+
} as any
|
|
142
|
+
expect(
|
|
143
|
+
parseProtocolData(pythonFile, contents, handleError, metadata)
|
|
144
|
+
).toStrictEqual({ metadata })
|
|
145
|
+
})
|
|
146
|
+
it(`should return JSON protocol file if valid json protocol given`, () => {
|
|
147
|
+
const contents = JSON.stringify(fixtureV5JsonProtocol)
|
|
148
|
+
const jsonFile = new File([contents], 'valid.json')
|
|
149
|
+
expect(parseProtocolData(jsonFile, contents, handleError)).toStrictEqual(
|
|
150
|
+
fixtureV5JsonProtocol
|
|
151
|
+
)
|
|
152
|
+
})
|
|
153
|
+
it(`should call handleError if given JSON protocol not valid`, () => {
|
|
154
|
+
const contents = JSON.stringify(fixtureV1JsonProtocol)
|
|
155
|
+
const jsonFile = new File([contents], 'invalid.json')
|
|
156
|
+
expect(parseProtocolData(jsonFile, contents, handleError)).toBe(null)
|
|
157
|
+
expect(handleError).toHaveBeenCalled()
|
|
158
|
+
})
|
|
159
|
+
it(`should call handleError if given invalid file extension`, () => {
|
|
160
|
+
const contents = JSON.stringify(fixtureV1JsonProtocol)
|
|
161
|
+
const jsonFile = new File([contents], 'invalid.foobar')
|
|
162
|
+
expect(parseProtocolData(jsonFile, contents, handleError)).toBe(null)
|
|
163
|
+
expect(handleError).toHaveBeenCalledWith('INVALID_FILE_TYPE')
|
|
164
|
+
})
|
|
165
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// volume helpers tests
|
|
2
|
+
import * as helpers from '..'
|
|
3
|
+
|
|
4
|
+
interface BaseSpec<T extends (...args: any) => any> {
|
|
5
|
+
name: string
|
|
6
|
+
func: T
|
|
7
|
+
input: Parameters<T>
|
|
8
|
+
expected: ReturnType<T>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type GetDisplayVolumeSpec = BaseSpec<typeof helpers.getDisplayVolume>
|
|
12
|
+
|
|
13
|
+
type GetAsciiVolumeUnitsSpec = BaseSpec<typeof helpers.getAsciiVolumeUnits>
|
|
14
|
+
|
|
15
|
+
type EnsureVolumeUnitsSpec = BaseSpec<typeof helpers.ensureVolumeUnits>
|
|
16
|
+
|
|
17
|
+
type TestSpec =
|
|
18
|
+
| GetDisplayVolumeSpec
|
|
19
|
+
| GetAsciiVolumeUnitsSpec
|
|
20
|
+
| EnsureVolumeUnitsSpec
|
|
21
|
+
|
|
22
|
+
describe('volume helpers', () => {
|
|
23
|
+
const SPECS: TestSpec[] = [
|
|
24
|
+
{
|
|
25
|
+
name: 'getDisplayVolume outputs µL string by default',
|
|
26
|
+
func: helpers.getDisplayVolume,
|
|
27
|
+
input: [380],
|
|
28
|
+
expected: '380',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'getDisplayVolume converts to mL',
|
|
32
|
+
func: helpers.getDisplayVolume,
|
|
33
|
+
input: [4200, 'mL'],
|
|
34
|
+
expected: '4.2',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'getDisplayVolume converts to L',
|
|
38
|
+
func: helpers.getDisplayVolume,
|
|
39
|
+
input: [800000, 'L'],
|
|
40
|
+
expected: '0.8',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'getDisplayVolume can round to fixed digits',
|
|
44
|
+
func: helpers.getDisplayVolume,
|
|
45
|
+
input: [123456789, 'L', 3],
|
|
46
|
+
expected: '123.457',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'getAsciiVolumeUnits converts µL to uL',
|
|
50
|
+
func: helpers.getAsciiVolumeUnits,
|
|
51
|
+
input: ['µL'],
|
|
52
|
+
expected: 'uL',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'getAsciiVolumeUnits leaves mL alone',
|
|
56
|
+
func: helpers.getAsciiVolumeUnits,
|
|
57
|
+
input: ['mL'],
|
|
58
|
+
expected: 'mL',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'getAsciiVolumeUnits leaves L alone',
|
|
62
|
+
func: helpers.getAsciiVolumeUnits,
|
|
63
|
+
input: ['L'],
|
|
64
|
+
expected: 'L',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'ensureVolumeUnits converts undefined to µL',
|
|
68
|
+
func: helpers.ensureVolumeUnits,
|
|
69
|
+
input: [undefined],
|
|
70
|
+
expected: 'µL',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'ensureVolumeUnits converts uL to µL',
|
|
74
|
+
func: helpers.ensureVolumeUnits,
|
|
75
|
+
input: ['uL'],
|
|
76
|
+
expected: 'µL',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'ensureVolumeUnits leaves mL alone',
|
|
80
|
+
func: helpers.ensureVolumeUnits,
|
|
81
|
+
input: ['mL'],
|
|
82
|
+
expected: 'mL',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'ensureVolumeUnits leaves L alone',
|
|
86
|
+
func: helpers.ensureVolumeUnits,
|
|
87
|
+
input: ['L'],
|
|
88
|
+
expected: 'L',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'ensureVolumeUnits converts ml to mL',
|
|
92
|
+
func: helpers.ensureVolumeUnits,
|
|
93
|
+
input: ['ml'],
|
|
94
|
+
expected: 'mL',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'ensureVolumeUnits leaves l to L',
|
|
98
|
+
func: helpers.ensureVolumeUnits,
|
|
99
|
+
input: ['l'],
|
|
100
|
+
expected: 'L',
|
|
101
|
+
},
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
SPECS.forEach(s => {
|
|
105
|
+
// @ts-expect-error(mc, 2021-04-27): rewrite as regular tests
|
|
106
|
+
it(`should ${s.name}`, () => expect(s.func(...s.input)).toEqual(s.expected))
|
|
107
|
+
})
|
|
108
|
+
})
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import pipetteNameSpecsFixtures from '../../../pipette/fixtures/name/pipetteNameSpecFixtures.json'
|
|
2
|
+
import fixture_12_trough from '../../../labware/fixtures/2/fixture_12_trough.json'
|
|
3
|
+
import fixture_96_plate from '../../../labware/fixtures/2/fixture_96_plate.json'
|
|
4
|
+
import fixture_384_plate from '../../../labware/fixtures/2/fixture_384_plate.json'
|
|
5
|
+
import fixture_overlappy_wellplate from '../../../labware/fixtures/2/fixture_overlappy_wellplate.json'
|
|
6
|
+
import { makeWellSetHelpers } from '../wellSets'
|
|
7
|
+
import { findWellAt } from '../getWellNamePerMultiTip'
|
|
8
|
+
import { get96Channel384WellPlateWells, orderWells } from '..'
|
|
9
|
+
|
|
10
|
+
import type { LabwareDefinition2, PipetteNameSpecs } from '../../types'
|
|
11
|
+
import type { WellSetHelpers } from '../wellSets'
|
|
12
|
+
|
|
13
|
+
const fixtureP10Single = pipetteNameSpecsFixtures.p10_single as PipetteNameSpecs
|
|
14
|
+
const fixtureP10Multi = pipetteNameSpecsFixtures.p10_multi as PipetteNameSpecs
|
|
15
|
+
const fixtureP100096 = (pipetteNameSpecsFixtures.p1000_96 as any) as PipetteNameSpecs
|
|
16
|
+
const fixture12Trough = fixture_12_trough as LabwareDefinition2
|
|
17
|
+
const fixture96Plate = fixture_96_plate as LabwareDefinition2
|
|
18
|
+
const fixture384Plate = fixture_384_plate as LabwareDefinition2
|
|
19
|
+
const fixtureOverlappyWellplate = fixture_overlappy_wellplate as LabwareDefinition2
|
|
20
|
+
const EIGHT_CHANNEL = 8
|
|
21
|
+
const NINETY_SIX_CHANNEL = 96
|
|
22
|
+
const wellsForReservoir = [
|
|
23
|
+
'A1',
|
|
24
|
+
'A2',
|
|
25
|
+
'A3',
|
|
26
|
+
'A4',
|
|
27
|
+
'A5',
|
|
28
|
+
'A6',
|
|
29
|
+
'A7',
|
|
30
|
+
'A8',
|
|
31
|
+
'A9',
|
|
32
|
+
'A10',
|
|
33
|
+
'A11',
|
|
34
|
+
'A12',
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
const wellsFor96WellPlate = [
|
|
38
|
+
'A1',
|
|
39
|
+
'B1',
|
|
40
|
+
'C1',
|
|
41
|
+
'D1',
|
|
42
|
+
'E1',
|
|
43
|
+
'F1',
|
|
44
|
+
'G1',
|
|
45
|
+
'H1',
|
|
46
|
+
'A2',
|
|
47
|
+
'B2',
|
|
48
|
+
'C2',
|
|
49
|
+
'D2',
|
|
50
|
+
'E2',
|
|
51
|
+
'F2',
|
|
52
|
+
'G2',
|
|
53
|
+
'H2',
|
|
54
|
+
'A3',
|
|
55
|
+
'B3',
|
|
56
|
+
'C3',
|
|
57
|
+
'D3',
|
|
58
|
+
'E3',
|
|
59
|
+
'F3',
|
|
60
|
+
'G3',
|
|
61
|
+
'H3',
|
|
62
|
+
'A4',
|
|
63
|
+
'B4',
|
|
64
|
+
'C4',
|
|
65
|
+
'D4',
|
|
66
|
+
'E4',
|
|
67
|
+
'F4',
|
|
68
|
+
'G4',
|
|
69
|
+
'H4',
|
|
70
|
+
'A5',
|
|
71
|
+
'B5',
|
|
72
|
+
'C5',
|
|
73
|
+
'D5',
|
|
74
|
+
'E5',
|
|
75
|
+
'F5',
|
|
76
|
+
'G5',
|
|
77
|
+
'H5',
|
|
78
|
+
'A6',
|
|
79
|
+
'B6',
|
|
80
|
+
'C6',
|
|
81
|
+
'D6',
|
|
82
|
+
'E6',
|
|
83
|
+
'F6',
|
|
84
|
+
'G6',
|
|
85
|
+
'H6',
|
|
86
|
+
'A7',
|
|
87
|
+
'B7',
|
|
88
|
+
'C7',
|
|
89
|
+
'D7',
|
|
90
|
+
'E7',
|
|
91
|
+
'F7',
|
|
92
|
+
'G7',
|
|
93
|
+
'H7',
|
|
94
|
+
'A8',
|
|
95
|
+
'B8',
|
|
96
|
+
'C8',
|
|
97
|
+
'D8',
|
|
98
|
+
'E8',
|
|
99
|
+
'F8',
|
|
100
|
+
'G8',
|
|
101
|
+
'H8',
|
|
102
|
+
'A9',
|
|
103
|
+
'B9',
|
|
104
|
+
'C9',
|
|
105
|
+
'D9',
|
|
106
|
+
'E9',
|
|
107
|
+
'F9',
|
|
108
|
+
'G9',
|
|
109
|
+
'H9',
|
|
110
|
+
'A10',
|
|
111
|
+
'B10',
|
|
112
|
+
'C10',
|
|
113
|
+
'D10',
|
|
114
|
+
'E10',
|
|
115
|
+
'F10',
|
|
116
|
+
'G10',
|
|
117
|
+
'H10',
|
|
118
|
+
'A11',
|
|
119
|
+
'B11',
|
|
120
|
+
'C11',
|
|
121
|
+
'D11',
|
|
122
|
+
'E11',
|
|
123
|
+
'F11',
|
|
124
|
+
'G11',
|
|
125
|
+
'H11',
|
|
126
|
+
'A12',
|
|
127
|
+
'B12',
|
|
128
|
+
'C12',
|
|
129
|
+
'D12',
|
|
130
|
+
'E12',
|
|
131
|
+
'F12',
|
|
132
|
+
'G12',
|
|
133
|
+
'H12',
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
describe('findWellAt', () => {
|
|
137
|
+
it('should determine if given (x, y) is within a rectangular well', () => {
|
|
138
|
+
const def: any = {
|
|
139
|
+
wells: {
|
|
140
|
+
A1: {
|
|
141
|
+
shape: 'rectangular',
|
|
142
|
+
x: 200,
|
|
143
|
+
y: 200,
|
|
144
|
+
xDimension: 10,
|
|
145
|
+
yDimension: 10,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
const middle = findWellAt(def, 200, 200)
|
|
150
|
+
expect(middle).toBe('A1')
|
|
151
|
+
|
|
152
|
+
const insideCornerNE = findWellAt(def, 200 - 4, 200 + 4)
|
|
153
|
+
expect(insideCornerNE).toEqual('A1')
|
|
154
|
+
|
|
155
|
+
// exactly at an edge doesn't count
|
|
156
|
+
const exactlyOnCornerNE = findWellAt(def, 200 - 5, 200 + 5)
|
|
157
|
+
expect(exactlyOnCornerNE).toBeUndefined()
|
|
158
|
+
|
|
159
|
+
const exactlyOnWEdge = findWellAt(def, 200, 200 - 5)
|
|
160
|
+
expect(exactlyOnWEdge).toBeUndefined()
|
|
161
|
+
|
|
162
|
+
const justOutsideToEast = findWellAt(def, 200 + 5.1, 200)
|
|
163
|
+
expect(justOutsideToEast).toBeUndefined()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('should determine if given (x, y) is within a circular well', () => {
|
|
167
|
+
const def: any = {
|
|
168
|
+
wells: {
|
|
169
|
+
A1: {
|
|
170
|
+
shape: 'circular',
|
|
171
|
+
x: 200,
|
|
172
|
+
y: 200,
|
|
173
|
+
diameter: 10,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
const middle = findWellAt(def, 200, 200)
|
|
178
|
+
expect(middle).toBe('A1')
|
|
179
|
+
|
|
180
|
+
const inside = findWellAt(def, 200 - 1, 200 + 1)
|
|
181
|
+
expect(inside).toEqual('A1')
|
|
182
|
+
|
|
183
|
+
// exactly at an edge doesn't count
|
|
184
|
+
const exactlyOnWEdge = findWellAt(def, 200, 200 - 5)
|
|
185
|
+
expect(exactlyOnWEdge).toBeUndefined()
|
|
186
|
+
|
|
187
|
+
const justOutsideToEast = findWellAt(def, 200 + 5.1, 200)
|
|
188
|
+
expect(justOutsideToEast).toBeUndefined()
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
describe('canPipetteUseLabware', () => {
|
|
192
|
+
let canPipetteUseLabware: WellSetHelpers['canPipetteUseLabware']
|
|
193
|
+
|
|
194
|
+
beforeEach(() => {
|
|
195
|
+
const helpers = makeWellSetHelpers()
|
|
196
|
+
canPipetteUseLabware = helpers.canPipetteUseLabware
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('returns false when wells are too close together for multi channel pipettes', () => {
|
|
200
|
+
const labwareDef = fixtureOverlappyWellplate
|
|
201
|
+
const pipette = fixtureP10Multi
|
|
202
|
+
const pipette96 = fixtureP100096
|
|
203
|
+
|
|
204
|
+
expect(canPipetteUseLabware(pipette, labwareDef)).toBe(false)
|
|
205
|
+
expect(canPipetteUseLabware(pipette96, labwareDef)).toBe(false)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('returns true when pipette is single channel', () => {
|
|
209
|
+
const labwareDef = fixtureOverlappyWellplate
|
|
210
|
+
const pipette = fixtureP10Single
|
|
211
|
+
|
|
212
|
+
expect(canPipetteUseLabware(pipette, labwareDef)).toBe(true)
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
describe('getWellSetForMultichannel (integration test)', () => {
|
|
217
|
+
let getWellSetForMultichannel: WellSetHelpers['getWellSetForMultichannel']
|
|
218
|
+
|
|
219
|
+
beforeEach(() => {
|
|
220
|
+
const helpers = makeWellSetHelpers()
|
|
221
|
+
getWellSetForMultichannel = helpers.getWellSetForMultichannel
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('96-flat', () => {
|
|
225
|
+
const labwareDef = fixture96Plate
|
|
226
|
+
|
|
227
|
+
expect(getWellSetForMultichannel(labwareDef, 'A1', EIGHT_CHANNEL)).toEqual([
|
|
228
|
+
'A1',
|
|
229
|
+
'B1',
|
|
230
|
+
'C1',
|
|
231
|
+
'D1',
|
|
232
|
+
'E1',
|
|
233
|
+
'F1',
|
|
234
|
+
'G1',
|
|
235
|
+
'H1',
|
|
236
|
+
])
|
|
237
|
+
|
|
238
|
+
expect(getWellSetForMultichannel(labwareDef, 'B1', EIGHT_CHANNEL)).toEqual([
|
|
239
|
+
'A1',
|
|
240
|
+
'B1',
|
|
241
|
+
'C1',
|
|
242
|
+
'D1',
|
|
243
|
+
'E1',
|
|
244
|
+
'F1',
|
|
245
|
+
'G1',
|
|
246
|
+
'H1',
|
|
247
|
+
])
|
|
248
|
+
|
|
249
|
+
expect(getWellSetForMultichannel(labwareDef, 'H1', EIGHT_CHANNEL)).toEqual([
|
|
250
|
+
'A1',
|
|
251
|
+
'B1',
|
|
252
|
+
'C1',
|
|
253
|
+
'D1',
|
|
254
|
+
'E1',
|
|
255
|
+
'F1',
|
|
256
|
+
'G1',
|
|
257
|
+
'H1',
|
|
258
|
+
])
|
|
259
|
+
|
|
260
|
+
expect(getWellSetForMultichannel(labwareDef, 'A2', EIGHT_CHANNEL)).toEqual([
|
|
261
|
+
'A2',
|
|
262
|
+
'B2',
|
|
263
|
+
'C2',
|
|
264
|
+
'D2',
|
|
265
|
+
'E2',
|
|
266
|
+
'F2',
|
|
267
|
+
'G2',
|
|
268
|
+
'H2',
|
|
269
|
+
])
|
|
270
|
+
|
|
271
|
+
// 96-channel
|
|
272
|
+
expect(
|
|
273
|
+
getWellSetForMultichannel(labwareDef, 'A1', NINETY_SIX_CHANNEL)
|
|
274
|
+
).toEqual(wellsFor96WellPlate)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('invalid well', () => {
|
|
278
|
+
const labwareDef = fixture96Plate
|
|
279
|
+
|
|
280
|
+
expect(
|
|
281
|
+
getWellSetForMultichannel(labwareDef, 'A13', EIGHT_CHANNEL)
|
|
282
|
+
).toBeFalsy()
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('trough-12row', () => {
|
|
286
|
+
const labwareDef = fixture12Trough
|
|
287
|
+
|
|
288
|
+
expect(getWellSetForMultichannel(labwareDef, 'A1', EIGHT_CHANNEL)).toEqual([
|
|
289
|
+
'A1',
|
|
290
|
+
'A1',
|
|
291
|
+
'A1',
|
|
292
|
+
'A1',
|
|
293
|
+
'A1',
|
|
294
|
+
'A1',
|
|
295
|
+
'A1',
|
|
296
|
+
'A1',
|
|
297
|
+
])
|
|
298
|
+
|
|
299
|
+
expect(getWellSetForMultichannel(labwareDef, 'A2', EIGHT_CHANNEL)).toEqual([
|
|
300
|
+
'A2',
|
|
301
|
+
'A2',
|
|
302
|
+
'A2',
|
|
303
|
+
'A2',
|
|
304
|
+
'A2',
|
|
305
|
+
'A2',
|
|
306
|
+
'A2',
|
|
307
|
+
'A2',
|
|
308
|
+
])
|
|
309
|
+
|
|
310
|
+
// 96-channel
|
|
311
|
+
expect(
|
|
312
|
+
getWellSetForMultichannel(labwareDef, 'A1', NINETY_SIX_CHANNEL)
|
|
313
|
+
).toEqual(wellsForReservoir)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('384-plate', () => {
|
|
317
|
+
const labwareDef = fixture384Plate
|
|
318
|
+
const well96Channel = 'A1'
|
|
319
|
+
const all384Wells = orderWells(labwareDef.ordering, 't2b', 'l2r')
|
|
320
|
+
const ninetySixChannelWells = get96Channel384WellPlateWells(
|
|
321
|
+
all384Wells,
|
|
322
|
+
well96Channel
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
expect(getWellSetForMultichannel(labwareDef, 'C1', EIGHT_CHANNEL)).toEqual([
|
|
326
|
+
'A1',
|
|
327
|
+
'C1',
|
|
328
|
+
'E1',
|
|
329
|
+
'G1',
|
|
330
|
+
'I1',
|
|
331
|
+
'K1',
|
|
332
|
+
'M1',
|
|
333
|
+
'O1',
|
|
334
|
+
])
|
|
335
|
+
|
|
336
|
+
expect(getWellSetForMultichannel(labwareDef, 'F2', EIGHT_CHANNEL)).toEqual([
|
|
337
|
+
'B2',
|
|
338
|
+
'D2',
|
|
339
|
+
'F2',
|
|
340
|
+
'H2',
|
|
341
|
+
'J2',
|
|
342
|
+
'L2',
|
|
343
|
+
'N2',
|
|
344
|
+
'P2',
|
|
345
|
+
])
|
|
346
|
+
|
|
347
|
+
// 96-channel
|
|
348
|
+
expect(
|
|
349
|
+
getWellSetForMultichannel(labwareDef, well96Channel, NINETY_SIX_CHANNEL)
|
|
350
|
+
).toEqual(ninetySixChannelWells)
|
|
351
|
+
})
|
|
352
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// TODO(jr, 10/19/23): should extend getWellNamePerMultiTip to use similar math for 96-channel and 384 well plates
|
|
2
|
+
// instead of special casing using this util
|
|
3
|
+
export function get96Channel384WellPlateWells(
|
|
4
|
+
all384Wells: string[],
|
|
5
|
+
well: string
|
|
6
|
+
): string[] {
|
|
7
|
+
const totalWells = 96 * 2 // multiplying 2 because we will remove either odd or even numbers
|
|
8
|
+
|
|
9
|
+
const filterWellsFor96Channel = (start: number, isOdd: boolean): string[] => {
|
|
10
|
+
const filteredWells: string[] = []
|
|
11
|
+
let count = start
|
|
12
|
+
for (let i = 0; i < totalWells; i++) {
|
|
13
|
+
if (count < all384Wells.length) {
|
|
14
|
+
const well = all384Wells[count]
|
|
15
|
+
const numberFromWell = well.match(/\d+/)
|
|
16
|
+
if (numberFromWell) {
|
|
17
|
+
const number = parseInt(numberFromWell[0])
|
|
18
|
+
if ((number % 2 === 1 && isOdd) || (number % 2 === 0 && !isOdd)) {
|
|
19
|
+
filteredWells.push(well)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
count += 2
|
|
23
|
+
} else {
|
|
24
|
+
break
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return filteredWells
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const allSetsOfWells = [
|
|
31
|
+
filterWellsFor96Channel(0, true),
|
|
32
|
+
filterWellsFor96Channel(1, true),
|
|
33
|
+
filterWellsFor96Channel(0, false),
|
|
34
|
+
filterWellsFor96Channel(1, false),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
for (const wells of allSetsOfWells) {
|
|
38
|
+
if (wells.includes(well)) {
|
|
39
|
+
return wells
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return []
|
|
44
|
+
}
|