smiles-js 2.0.3 → 2.2.0

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.
Files changed (49) hide show
  1. package/API.md +162 -0
  2. package/README.md +39 -0
  3. package/docs/MIRROR_PLAN.md +204 -0
  4. package/docs/smiles.peggy +215 -0
  5. package/package.json +1 -1
  6. package/scripts/coverage-summary.js +1 -1
  7. package/src/codegen/branch-crossing-ring.js +27 -6
  8. package/src/codegen/interleaved-fused-ring.js +24 -0
  9. package/src/decompiler.js +236 -51
  10. package/src/decompiler.test.js +232 -60
  11. package/src/fragment.test.js +7 -2
  12. package/src/manipulation.js +409 -4
  13. package/src/manipulation.test.js +359 -1
  14. package/src/method-attachers.js +37 -8
  15. package/src/node-creators.js +7 -0
  16. package/src/parser/ast-builder.js +23 -8
  17. package/src/parser/ring-group-builder.js +14 -2
  18. package/src/parser/ring-utils.js +28 -0
  19. package/test-integration/__snapshots__/acetaminophen.test.js.snap +20 -0
  20. package/test-integration/__snapshots__/adjuvant-analgesics.test.js.snap +63 -1
  21. package/test-integration/__snapshots__/cholesterol-drugs.test.js.snap +437 -0
  22. package/test-integration/__snapshots__/dexamethasone.test.js.snap +31 -0
  23. package/test-integration/__snapshots__/endocannabinoids.test.js.snap +79 -2
  24. package/test-integration/__snapshots__/endogenous-opioids.test.js.snap +1116 -0
  25. package/test-integration/__snapshots__/hypertension-medication.test.js.snap +70 -1
  26. package/test-integration/__snapshots__/local-anesthetics.test.js.snap +97 -0
  27. package/test-integration/__snapshots__/nsaids-otc.test.js.snap +61 -1
  28. package/test-integration/__snapshots__/nsaids-prescription.test.js.snap +115 -2
  29. package/test-integration/__snapshots__/opioids.test.js.snap +113 -4
  30. package/test-integration/__snapshots__/steroids.test.js.snap +381 -2
  31. package/test-integration/acetaminophen.test.js +15 -3
  32. package/test-integration/adjuvant-analgesics.test.js +43 -7
  33. package/test-integration/cholesterol-drugs.test.js +127 -20
  34. package/test-integration/cholesterol.test.js +112 -0
  35. package/test-integration/dexamethasone.test.js +8 -2
  36. package/test-integration/endocannabinoids.test.js +48 -12
  37. package/test-integration/endogenous-opioids.smiles.js +32 -0
  38. package/test-integration/endogenous-opioids.test.js +192 -0
  39. package/test-integration/hypertension-medication.test.js +32 -8
  40. package/test-integration/local-anesthetics.smiles.js +33 -0
  41. package/test-integration/local-anesthetics.test.js +64 -16
  42. package/test-integration/mirror.test.js +151 -0
  43. package/test-integration/nsaids-otc.test.js +40 -10
  44. package/test-integration/nsaids-prescription.test.js +72 -18
  45. package/test-integration/opioids.test.js +56 -14
  46. package/test-integration/polymer.test.js +148 -0
  47. package/test-integration/steroids.test.js +112 -28
  48. package/test-integration/utils.js +4 -2
  49. package/todo +2 -3
@@ -26,6 +26,12 @@ describe('Celecoxib Integration Test', () => {
26
26
  expect(code).toMatchSnapshot();
27
27
  });
28
28
 
29
+ test('generates valid verbose code via toCode()', () => {
30
+ const ast = parse(CELECOXIB_SMILES);
31
+ const code = ast.toCode('v', { verbose: true });
32
+ expect(code).toMatchSnapshot();
33
+ });
34
+
29
35
  test('generated code is valid JavaScript', () => {
30
36
  const ast = parse(CELECOXIB_SMILES);
31
37
  const code = ast.toCode('v');
@@ -33,14 +39,14 @@ describe('Celecoxib Integration Test', () => {
33
39
 
34
40
  let factory;
35
41
  expect(() => {
36
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
42
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
37
43
  }).not.toThrow();
38
44
  expect(typeof factory).toBe('function');
39
45
  });
40
46
 
41
47
  test('codegen round-trip: generated code produces valid SMILES', () => {
42
48
  const ast = parse(CELECOXIB_SMILES);
43
- const code = ast.toCode('v');
49
+ const code = ast.toCode('v', { verbose: true });
44
50
  const executableCode = stripExports(code);
45
51
 
46
52
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -66,6 +72,12 @@ describe('Meloxicam Integration Test', () => {
66
72
  expect(code).toMatchSnapshot();
67
73
  });
68
74
 
75
+ test('generates valid verbose code via toCode()', () => {
76
+ const ast = parse(MELOXICAM_SMILES);
77
+ const code = ast.toCode('v', { verbose: true });
78
+ expect(code).toMatchSnapshot();
79
+ });
80
+
69
81
  test('generated code is valid JavaScript', () => {
70
82
  const ast = parse(MELOXICAM_SMILES);
71
83
  const code = ast.toCode('v');
@@ -73,14 +85,14 @@ describe('Meloxicam Integration Test', () => {
73
85
 
74
86
  let factory;
75
87
  expect(() => {
76
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
88
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
77
89
  }).not.toThrow();
78
90
  expect(typeof factory).toBe('function');
79
91
  });
80
92
 
81
93
  test('codegen round-trip: generated code produces valid SMILES', () => {
82
94
  const ast = parse(MELOXICAM_SMILES);
83
- const code = ast.toCode('v');
95
+ const code = ast.toCode('v', { verbose: true });
84
96
  const executableCode = stripExports(code);
85
97
 
86
98
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -106,6 +118,12 @@ describe('Piroxicam Integration Test', () => {
106
118
  expect(code).toMatchSnapshot();
107
119
  });
108
120
 
121
+ test('generates valid verbose code via toCode()', () => {
122
+ const ast = parse(PIROXICAM_SMILES);
123
+ const code = ast.toCode('v', { verbose: true });
124
+ expect(code).toMatchSnapshot();
125
+ });
126
+
109
127
  test('generated code is valid JavaScript', () => {
110
128
  const ast = parse(PIROXICAM_SMILES);
111
129
  const code = ast.toCode('v');
@@ -113,14 +131,14 @@ describe('Piroxicam Integration Test', () => {
113
131
 
114
132
  let factory;
115
133
  expect(() => {
116
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
134
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
117
135
  }).not.toThrow();
118
136
  expect(typeof factory).toBe('function');
119
137
  });
120
138
 
121
139
  test('codegen round-trip: generated code produces valid SMILES', () => {
122
140
  const ast = parse(PIROXICAM_SMILES);
123
- const code = ast.toCode('v');
141
+ const code = ast.toCode('v', { verbose: true });
124
142
  const executableCode = stripExports(code);
125
143
 
126
144
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -146,6 +164,12 @@ describe('Etodolac Integration Test', () => {
146
164
  expect(code).toMatchSnapshot();
147
165
  });
148
166
 
167
+ test('generates valid verbose code via toCode()', () => {
168
+ const ast = parse(ETODOLAC_SMILES);
169
+ const code = ast.toCode('v', { verbose: true });
170
+ expect(code).toMatchSnapshot();
171
+ });
172
+
149
173
  test('generated code is valid JavaScript', () => {
150
174
  const ast = parse(ETODOLAC_SMILES);
151
175
  const code = ast.toCode('v');
@@ -153,14 +177,14 @@ describe('Etodolac Integration Test', () => {
153
177
 
154
178
  let factory;
155
179
  expect(() => {
156
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
180
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
157
181
  }).not.toThrow();
158
182
  expect(typeof factory).toBe('function');
159
183
  });
160
184
 
161
185
  test('codegen round-trip: generated code produces valid SMILES', () => {
162
186
  const ast = parse(ETODOLAC_SMILES);
163
- const code = ast.toCode('v');
187
+ const code = ast.toCode('v', { verbose: true });
164
188
  const executableCode = stripExports(code);
165
189
 
166
190
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -186,6 +210,12 @@ describe('Ketorolac Integration Test', () => {
186
210
  expect(code).toMatchSnapshot();
187
211
  });
188
212
 
213
+ test('generates valid verbose code via toCode()', () => {
214
+ const ast = parse(KETOROLAC_SMILES);
215
+ const code = ast.toCode('v', { verbose: true });
216
+ expect(code).toMatchSnapshot();
217
+ });
218
+
189
219
  test('generated code is valid JavaScript', () => {
190
220
  const ast = parse(KETOROLAC_SMILES);
191
221
  const code = ast.toCode('v');
@@ -193,14 +223,14 @@ describe('Ketorolac Integration Test', () => {
193
223
 
194
224
  let factory;
195
225
  expect(() => {
196
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
226
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
197
227
  }).not.toThrow();
198
228
  expect(typeof factory).toBe('function');
199
229
  });
200
230
 
201
231
  test('codegen round-trip: generated code produces valid SMILES', () => {
202
232
  const ast = parse(KETOROLAC_SMILES);
203
- const code = ast.toCode('v');
233
+ const code = ast.toCode('v', { verbose: true });
204
234
  const executableCode = stripExports(code);
205
235
 
206
236
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -227,6 +257,12 @@ describe('Rofecoxib Integration Test', () => {
227
257
  expect(code).toMatchSnapshot();
228
258
  });
229
259
 
260
+ test('generates valid verbose code via toCode()', () => {
261
+ const ast = parse(ROFECOXIB_SMILES);
262
+ const code = ast.toCode('v', { verbose: true });
263
+ expect(code).toMatchSnapshot();
264
+ });
265
+
230
266
  test('generated code is valid JavaScript', () => {
231
267
  const ast = parse(ROFECOXIB_SMILES);
232
268
  const code = ast.toCode('v');
@@ -234,14 +270,14 @@ describe('Rofecoxib Integration Test', () => {
234
270
 
235
271
  let factory;
236
272
  expect(() => {
237
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
273
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
238
274
  }).not.toThrow();
239
275
  expect(typeof factory).toBe('function');
240
276
  });
241
277
 
242
278
  test('codegen round-trip: generated code produces valid SMILES', () => {
243
279
  const ast = parse(ROFECOXIB_SMILES);
244
- const code = ast.toCode('v');
280
+ const code = ast.toCode('v', { verbose: true });
245
281
  const executableCode = stripExports(code);
246
282
 
247
283
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -267,6 +303,12 @@ describe('Etoricoxib Integration Test', () => {
267
303
  expect(code).toMatchSnapshot();
268
304
  });
269
305
 
306
+ test('generates valid verbose code via toCode()', () => {
307
+ const ast = parse(ETORICOXIB_SMILES);
308
+ const code = ast.toCode('v', { verbose: true });
309
+ expect(code).toMatchSnapshot();
310
+ });
311
+
270
312
  test('generated code is valid JavaScript', () => {
271
313
  const ast = parse(ETORICOXIB_SMILES);
272
314
  const code = ast.toCode('v');
@@ -274,14 +316,14 @@ describe('Etoricoxib Integration Test', () => {
274
316
 
275
317
  let factory;
276
318
  expect(() => {
277
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
319
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
278
320
  }).not.toThrow();
279
321
  expect(typeof factory).toBe('function');
280
322
  });
281
323
 
282
324
  test('codegen round-trip: generated code produces valid SMILES', () => {
283
325
  const ast = parse(ETORICOXIB_SMILES);
284
- const code = ast.toCode('v');
326
+ const code = ast.toCode('v', { verbose: true });
285
327
  const executableCode = stripExports(code);
286
328
 
287
329
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -307,6 +349,12 @@ describe('Nabumetone Integration Test', () => {
307
349
  expect(code).toMatchSnapshot();
308
350
  });
309
351
 
352
+ test('generates valid verbose code via toCode()', () => {
353
+ const ast = parse(NABUMETONE_SMILES);
354
+ const code = ast.toCode('v', { verbose: true });
355
+ expect(code).toMatchSnapshot();
356
+ });
357
+
310
358
  test('generated code is valid JavaScript', () => {
311
359
  const ast = parse(NABUMETONE_SMILES);
312
360
  const code = ast.toCode('v');
@@ -314,14 +362,14 @@ describe('Nabumetone Integration Test', () => {
314
362
 
315
363
  let factory;
316
364
  expect(() => {
317
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
365
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
318
366
  }).not.toThrow();
319
367
  expect(typeof factory).toBe('function');
320
368
  });
321
369
 
322
370
  test('codegen round-trip: generated code produces valid SMILES', () => {
323
371
  const ast = parse(NABUMETONE_SMILES);
324
- const code = ast.toCode('v');
372
+ const code = ast.toCode('v', { verbose: true });
325
373
  const executableCode = stripExports(code);
326
374
 
327
375
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -348,6 +396,12 @@ describe('Oxaprozin Integration Test', () => {
348
396
  expect(code).toMatchSnapshot();
349
397
  });
350
398
 
399
+ test('generates valid verbose code via toCode()', () => {
400
+ const ast = parse(OXAPROZIN_SMILES);
401
+ const code = ast.toCode('v', { verbose: true });
402
+ expect(code).toMatchSnapshot();
403
+ });
404
+
351
405
  test('generated code is valid JavaScript', () => {
352
406
  const ast = parse(OXAPROZIN_SMILES);
353
407
  const code = ast.toCode('v');
@@ -355,14 +409,14 @@ describe('Oxaprozin Integration Test', () => {
355
409
 
356
410
  let factory;
357
411
  expect(() => {
358
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
412
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
359
413
  }).not.toThrow();
360
414
  expect(typeof factory).toBe('function');
361
415
  });
362
416
 
363
417
  test('codegen round-trip: generated code produces valid SMILES', () => {
364
418
  const ast = parse(OXAPROZIN_SMILES);
365
- const code = ast.toCode('v');
419
+ const code = ast.toCode('v', { verbose: true });
366
420
  const executableCode = stripExports(code);
367
421
 
368
422
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -23,6 +23,12 @@ describe('Morphine Integration Test', () => {
23
23
  expect(code).toMatchSnapshot();
24
24
  });
25
25
 
26
+ test('generates valid verbose code via toCode()', () => {
27
+ const ast = parse(MORPHINE_SMILES);
28
+ const code = ast.toCode('v', { verbose: true });
29
+ expect(code).toMatchSnapshot();
30
+ });
31
+
26
32
  test('generated code is valid JavaScript', () => {
27
33
  const ast = parse(MORPHINE_SMILES);
28
34
  const code = ast.toCode('v');
@@ -30,14 +36,14 @@ describe('Morphine Integration Test', () => {
30
36
 
31
37
  let factory;
32
38
  expect(() => {
33
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
39
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
34
40
  }).not.toThrow();
35
41
  expect(typeof factory).toBe('function');
36
42
  });
37
43
 
38
44
  test('codegen round-trip: generated code produces valid SMILES', () => {
39
45
  const ast = parse(MORPHINE_SMILES);
40
- const code = ast.toCode('v');
46
+ const code = ast.toCode('v', { verbose: true });
41
47
  const executableCode = stripExports(code);
42
48
 
43
49
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -63,6 +69,12 @@ describe('Codeine Integration Test', () => {
63
69
  expect(code).toMatchSnapshot();
64
70
  });
65
71
 
72
+ test('generates valid verbose code via toCode()', () => {
73
+ const ast = parse(CODEINE_SMILES);
74
+ const code = ast.toCode('v', { verbose: true });
75
+ expect(code).toMatchSnapshot();
76
+ });
77
+
66
78
  test('generated code is valid JavaScript', () => {
67
79
  const ast = parse(CODEINE_SMILES);
68
80
  const code = ast.toCode('v');
@@ -70,14 +82,14 @@ describe('Codeine Integration Test', () => {
70
82
 
71
83
  let factory;
72
84
  expect(() => {
73
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
85
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
74
86
  }).not.toThrow();
75
87
  expect(typeof factory).toBe('function');
76
88
  });
77
89
 
78
90
  test('codegen round-trip: generated code produces valid SMILES', () => {
79
91
  const ast = parse(CODEINE_SMILES);
80
- const code = ast.toCode('v');
92
+ const code = ast.toCode('v', { verbose: true });
81
93
  const executableCode = stripExports(code);
82
94
 
83
95
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -102,6 +114,12 @@ describe('Oxycodone Integration Test', () => {
102
114
  expect(code).toMatchSnapshot();
103
115
  });
104
116
 
117
+ test('generates valid verbose code via toCode()', () => {
118
+ const ast = parse(OXYCODONE_SMILES);
119
+ const code = ast.toCode('v', { verbose: true });
120
+ expect(code).toMatchSnapshot();
121
+ });
122
+
105
123
  test('generated code is valid JavaScript', () => {
106
124
  const ast = parse(OXYCODONE_SMILES);
107
125
  const code = ast.toCode('v');
@@ -109,14 +127,14 @@ describe('Oxycodone Integration Test', () => {
109
127
 
110
128
  let factory;
111
129
  expect(() => {
112
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
130
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
113
131
  }).not.toThrow();
114
132
  expect(typeof factory).toBe('function');
115
133
  });
116
134
 
117
135
  test('codegen round-trip: generated code produces valid SMILES', () => {
118
136
  const ast = parse(OXYCODONE_SMILES);
119
- const code = ast.toCode('v');
137
+ const code = ast.toCode('v', { verbose: true });
120
138
  const executableCode = stripExports(code);
121
139
 
122
140
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -141,6 +159,12 @@ describe('Hydrocodone Integration Test', () => {
141
159
  expect(code).toMatchSnapshot();
142
160
  });
143
161
 
162
+ test('generates valid verbose code via toCode()', () => {
163
+ const ast = parse(HYDROCODONE_SMILES);
164
+ const code = ast.toCode('v', { verbose: true });
165
+ expect(code).toMatchSnapshot();
166
+ });
167
+
144
168
  test('generated code is valid JavaScript', () => {
145
169
  const ast = parse(HYDROCODONE_SMILES);
146
170
  const code = ast.toCode('v');
@@ -148,14 +172,14 @@ describe('Hydrocodone Integration Test', () => {
148
172
 
149
173
  let factory;
150
174
  expect(() => {
151
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
175
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
152
176
  }).not.toThrow();
153
177
  expect(typeof factory).toBe('function');
154
178
  });
155
179
 
156
180
  test('codegen round-trip: generated code produces valid SMILES', () => {
157
181
  const ast = parse(HYDROCODONE_SMILES);
158
- const code = ast.toCode('v');
182
+ const code = ast.toCode('v', { verbose: true });
159
183
  const executableCode = stripExports(code);
160
184
 
161
185
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -181,6 +205,12 @@ describe('Fentanyl Integration Test', () => {
181
205
  expect(code).toMatchSnapshot();
182
206
  });
183
207
 
208
+ test('generates valid verbose code via toCode()', () => {
209
+ const ast = parse(FENTANYL_SMILES);
210
+ const code = ast.toCode('v', { verbose: true });
211
+ expect(code).toMatchSnapshot();
212
+ });
213
+
184
214
  test('generated code is valid JavaScript', () => {
185
215
  const ast = parse(FENTANYL_SMILES);
186
216
  const code = ast.toCode('v');
@@ -188,14 +218,14 @@ describe('Fentanyl Integration Test', () => {
188
218
 
189
219
  let factory;
190
220
  expect(() => {
191
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
221
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
192
222
  }).not.toThrow();
193
223
  expect(typeof factory).toBe('function');
194
224
  });
195
225
 
196
226
  test('codegen round-trip: generated code produces valid SMILES', () => {
197
227
  const ast = parse(FENTANYL_SMILES);
198
- const code = ast.toCode('v');
228
+ const code = ast.toCode('v', { verbose: true });
199
229
  const executableCode = stripExports(code);
200
230
 
201
231
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -221,6 +251,12 @@ describe('Tramadol Integration Test', () => {
221
251
  expect(code).toMatchSnapshot();
222
252
  });
223
253
 
254
+ test('generates valid verbose code via toCode()', () => {
255
+ const ast = parse(TRAMADOL_SMILES);
256
+ const code = ast.toCode('v', { verbose: true });
257
+ expect(code).toMatchSnapshot();
258
+ });
259
+
224
260
  test('generated code is valid JavaScript', () => {
225
261
  const ast = parse(TRAMADOL_SMILES);
226
262
  const code = ast.toCode('v');
@@ -228,14 +264,14 @@ describe('Tramadol Integration Test', () => {
228
264
 
229
265
  let factory;
230
266
  expect(() => {
231
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
267
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
232
268
  }).not.toThrow();
233
269
  expect(typeof factory).toBe('function');
234
270
  });
235
271
 
236
272
  test('codegen round-trip: generated code produces valid SMILES', () => {
237
273
  const ast = parse(TRAMADOL_SMILES);
238
- const code = ast.toCode('v');
274
+ const code = ast.toCode('v', { verbose: true });
239
275
  const executableCode = stripExports(code);
240
276
 
241
277
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -261,6 +297,12 @@ describe('Methadone Integration Test', () => {
261
297
  expect(code).toMatchSnapshot();
262
298
  });
263
299
 
300
+ test('generates valid verbose code via toCode()', () => {
301
+ const ast = parse(METHADONE_SMILES);
302
+ const code = ast.toCode('v', { verbose: true });
303
+ expect(code).toMatchSnapshot();
304
+ });
305
+
264
306
  test('generated code is valid JavaScript', () => {
265
307
  const ast = parse(METHADONE_SMILES);
266
308
  const code = ast.toCode('v');
@@ -268,14 +310,14 @@ describe('Methadone Integration Test', () => {
268
310
 
269
311
  let factory;
270
312
  expect(() => {
271
- factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
313
+ factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
272
314
  }).not.toThrow();
273
315
  expect(typeof factory).toBe('function');
274
316
  });
275
317
 
276
318
  test('codegen round-trip: generated code produces valid SMILES', () => {
277
319
  const ast = parse(METHADONE_SMILES);
278
- const code = ast.toCode('v');
320
+ const code = ast.toCode('v', { verbose: true });
279
321
  const executableCode = stripExports(code);
280
322
 
281
323
  const varMatch = code.match(/export const (v\d+) = /g);
@@ -0,0 +1,148 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { Ring, Linear, Molecule } from '../src/constructors.js';
3
+
4
+ describe('Polymer repeat() Integration', () => {
5
+ test('polyethylene trimer: -[CH2CH2]3-', () => {
6
+ const ethylene = Linear(['C', 'C']);
7
+ const pe = ethylene.repeat(3, 1, 2);
8
+ expect(pe.smiles).toBe('CCCCCC');
9
+ });
10
+
11
+ test('polystyrene dimer: styrene with phenyl pendant', () => {
12
+ const styrene = Linear(['C', 'C']).attach(
13
+ 2,
14
+ Ring({ atoms: 'c', size: 6 }),
15
+ );
16
+ const ps = styrene.repeat(2, 1, 2);
17
+ expect(ps.smiles).toBe('CC(c1ccccc1)CC(c2ccccc2)');
18
+ });
19
+
20
+ test('polystyrene trimer', () => {
21
+ const styrene = Linear(['C', 'C']).attach(
22
+ 2,
23
+ Ring({ atoms: 'c', size: 6 }),
24
+ );
25
+ const ps = styrene.repeat(3, 1, 2);
26
+ expect(ps.smiles).toBe('CC(c1ccccc1)CC(c2ccccc2)CC(c3ccccc3)');
27
+ });
28
+
29
+ test('poly(vinyl alcohol) dimer: -[CH2CH(OH)]2-', () => {
30
+ const vinylAlcohol = Linear(['C', 'C']).attach(
31
+ 2,
32
+ Linear(['O']),
33
+ );
34
+ const pva = vinylAlcohol.repeat(2, 1, 2);
35
+ expect(pva.smiles).toBe('CC(O)CC(O)');
36
+ });
37
+
38
+ test('biphenyl: two linked benzene rings', () => {
39
+ const benzene = Ring({ atoms: 'c', size: 6 });
40
+ const biphenyl = benzene.repeat(2, 1, 6);
41
+ expect(biphenyl.smiles).toBe('c1ccccc1c2ccccc2');
42
+ });
43
+
44
+ test('terphenyl: three linked benzene rings', () => {
45
+ const benzene = Ring({ atoms: 'c', size: 6 });
46
+ const terphenyl = benzene.repeat(3, 1, 6);
47
+ expect(terphenyl.smiles).toBe('c1ccccc1c2ccccc2c3ccccc3');
48
+ });
49
+
50
+ test('bipyridine: two linked pyridine rings', () => {
51
+ const pyridine = Ring({
52
+ atoms: 'c',
53
+ size: 6,
54
+ substitutions: { 3: 'n' },
55
+ });
56
+ const bipyridine = pyridine.repeat(2, 1, 6);
57
+ expect(bipyridine.smiles).toBe('c1cnccc1c2cnccc2');
58
+ });
59
+
60
+ test('polyoxymethylene trimer: -[CH2O]3-', () => {
61
+ const oxymethylene = Linear(['C', 'O']);
62
+ const pom = oxymethylene.repeat(3, 1, 2);
63
+ expect(pom.smiles).toBe('COCOCO');
64
+ });
65
+
66
+ test('nylon 6,6 repeating unit dimer', () => {
67
+ const unit = Molecule([
68
+ Linear(['N', 'C'], [null, '=']),
69
+ Linear(['C', 'C', 'C', 'C']),
70
+ Linear(['C', 'N'], ['=', null]),
71
+ Linear(['C', 'C', 'C', 'C', 'C', 'C']),
72
+ ]);
73
+ const dimer = unit.repeat(2, 1, 1);
74
+ expect(dimer.type).toBe('molecule');
75
+ expect(dimer.components).toHaveLength(2);
76
+ expect(dimer.smiles).toBe('N=CCCCC=CNCCCCCCN=CCCCC=CNCCCCCC');
77
+ });
78
+ });
79
+
80
+ describe('Polymer fusedRepeat() Integration', () => {
81
+ test('naphthalene: 2 fused aromatic rings', () => {
82
+ const benzene = Ring({ atoms: 'c', size: 6 });
83
+ const naphthalene = benzene.fusedRepeat(2, 4);
84
+ expect(naphthalene.type).toBe('fused_ring');
85
+ expect(naphthalene.rings).toHaveLength(2);
86
+ expect(naphthalene.rings[0].ringNumber).toBe(1);
87
+ expect(naphthalene.rings[1].ringNumber).toBe(2);
88
+ });
89
+
90
+ test('anthracene: 3 fused aromatic rings', () => {
91
+ const benzene = Ring({ atoms: 'c', size: 6 });
92
+ const anthracene = benzene.fusedRepeat(3, 4);
93
+ expect(anthracene.type).toBe('fused_ring');
94
+ expect(anthracene.rings).toHaveLength(3);
95
+ });
96
+
97
+ test('tetracene: 4 fused aromatic rings', () => {
98
+ const benzene = Ring({ atoms: 'c', size: 6 });
99
+ const tetracene = benzene.fusedRepeat(4, 4);
100
+ expect(tetracene.type).toBe('fused_ring');
101
+ expect(tetracene.rings).toHaveLength(4);
102
+ });
103
+
104
+ test('fused cyclohexane dimer (decalin-like)', () => {
105
+ const cyclohexane = Ring({ atoms: 'C', size: 6 });
106
+ const decalin = cyclohexane.fusedRepeat(2, 4);
107
+ expect(decalin.type).toBe('fused_ring');
108
+ expect(decalin.rings).toHaveLength(2);
109
+ });
110
+
111
+ test('fused 5-membered rings (pentalene-like)', () => {
112
+ const cp = Ring({ atoms: 'C', size: 5 });
113
+ const pentalene = cp.fusedRepeat(2, 3);
114
+ expect(pentalene.type).toBe('fused_ring');
115
+ expect(pentalene.rings).toHaveLength(2);
116
+ });
117
+ });
118
+
119
+ describe('Polymer edge cases', () => {
120
+ test('repeat n=1 returns clone, not original', () => {
121
+ const benzene = Ring({ atoms: 'c', size: 6 });
122
+ const clone = benzene.repeat(1, 1, 6);
123
+ expect(clone.smiles).toBe('c1ccccc1');
124
+ expect(clone).not.toBe(benzene);
125
+ });
126
+
127
+ test('fusedRepeat n=1 returns ring clone', () => {
128
+ const benzene = Ring({ atoms: 'c', size: 6 });
129
+ const clone = benzene.fusedRepeat(1, 4);
130
+ expect(clone.smiles).toBe('c1ccccc1');
131
+ expect(clone.type).toBe('ring');
132
+ expect(clone).not.toBe(benzene);
133
+ });
134
+
135
+ test('large repeat count (n=10)', () => {
136
+ const ethylene = Linear(['C', 'C']);
137
+ const pe10 = ethylene.repeat(10, 1, 2);
138
+ expect(pe10.smiles).toBe('C'.repeat(20));
139
+ });
140
+
141
+ test('repeat preserves immutability of original', () => {
142
+ const benzene = Ring({ atoms: 'c', size: 6 });
143
+ const original = benzene.smiles;
144
+ benzene.repeat(5, 1, 6);
145
+ benzene.fusedRepeat(3, 4);
146
+ expect(benzene.smiles).toBe(original);
147
+ });
148
+ });