Skip to content

Commit 7a41b90

Browse files
committed
Preserve generated TOML fields in E2E fixtures
1 parent 7dedb88 commit 7a41b90

2 files changed

Lines changed: 112 additions & 10 deletions

File tree

packages/e2e/setup/app.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import {authFixture} from './auth.js'
33
import {getLastPageStatus, isVisibleWithin, navigateToDashboard, refreshIfPageError} from './browser.js'
44
import {CLI_TIMEOUT, BROWSER_TIMEOUT} from './constants.js'
5-
import {updateTomlValues} from '@shopify/toml-patch'
65
import * as toml from '@iarna/toml'
76
import * as path from 'path'
87
import * as fs from 'fs'
@@ -112,17 +111,41 @@ export function extractClientId(appDir: string): string {
112111
}
113112

114113
/**
115-
* Overwrite a created app's shopify.app.toml with a fixture TOML template.
116-
* Uses toml-patch to surgically set client_id and name while
117-
* preserving comments and formatting in the fixture file.
114+
* Merge an E2E TOML fixture into the app config generated by the template.
115+
* Template-owned fields not mentioned by the fixture are preserved.
118116
*/
117+
type TomlTable = Parameters<typeof toml.stringify>[0]
118+
119+
function isTomlTable(value: unknown): value is TomlTable {
120+
return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)
121+
}
122+
123+
function mergeTomlTables(base: TomlTable, overlay: TomlTable): TomlTable {
124+
const merged: TomlTable = {...base}
125+
for (const [key, overlayValue] of Object.entries(overlay)) {
126+
const baseValue = merged[key]
127+
merged[key] =
128+
isTomlTable(baseValue) && isTomlTable(overlayValue) ? mergeTomlTables(baseValue, overlayValue) : overlayValue
129+
}
130+
return merged
131+
}
132+
133+
export function mergeFixtureToml(generatedTomlContent: string, fixtureTomlContent: string, name: string): string {
134+
const generated = toml.parse(generatedTomlContent)
135+
const clientId = generated.client_id as string | undefined
136+
if (!clientId) {
137+
throw new Error('Could not find client_id in generated shopify.app.toml')
138+
}
139+
140+
const fixture = toml.parse(fixtureTomlContent)
141+
142+
return toml.stringify(mergeTomlTables(generated, {...fixture, client_id: clientId, name}))
143+
}
144+
119145
export function injectFixtureToml(appDir: string, fixtureTomlContent: string, name: string): void {
120-
const clientId = extractClientId(appDir)
121-
const patched = updateTomlValues(fixtureTomlContent, [
122-
[['client_id'], clientId],
123-
[['name'], name],
124-
])
125-
fs.writeFileSync(path.join(appDir, 'shopify.app.toml'), patched)
146+
const tomlPath = path.join(appDir, 'shopify.app.toml')
147+
const patched = mergeFixtureToml(fs.readFileSync(tomlPath, 'utf8'), fixtureTomlContent, name)
148+
fs.writeFileSync(tomlPath, patched)
126149
}
127150

128151
export async function generateExtension(
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* eslint-disable no-restricted-imports */
2+
import {mergeFixtureToml} from '../setup/app.js'
3+
import {expect, test} from '@playwright/test'
4+
import * as toml from '@iarna/toml'
5+
import * as fs from 'fs'
6+
import * as path from 'path'
7+
import {fileURLToPath} from 'url'
8+
9+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
10+
const VALID_APP_FIXTURE = fs.readFileSync(path.join(__dirname, '../data/valid-app/shopify.app.toml'), 'utf8')
11+
12+
test.describe('fixture TOML', () => {
13+
test('merges fixture values without dropping template-owned fields', () => {
14+
const generatedToml = `
15+
client_id = "generated-client-id"
16+
name = "Generated app name"
17+
application_url = "https://template.example.com"
18+
19+
[access_scopes]
20+
scopes = "read_products"
21+
22+
[sidekick]
23+
extensions_summary = "Template-provided Sidekick summary"
24+
template_owned = true
25+
`.trimStart()
26+
27+
const fixtureToml = `
28+
client_id = "placeholder"
29+
name = "placeholder"
30+
application_url = "https://fixture.example.com"
31+
32+
[build]
33+
include_config_on_deploy = true
34+
35+
[sidekick]
36+
extensions_summary = "Fixture-provided Sidekick summary"
37+
`.trimStart()
38+
39+
const mergedToml = mergeFixtureToml(generatedToml, fixtureToml, 'E2E merged app')
40+
const parsed = toml.parse(mergedToml)
41+
42+
expect(parsed.client_id).toBe('generated-client-id')
43+
expect(parsed.name).toBe('E2E merged app')
44+
expect(parsed.application_url).toBe('https://fixture.example.com')
45+
expect(parsed.access_scopes).toEqual({scopes: 'read_products'})
46+
expect(parsed.sidekick).toEqual({
47+
extensions_summary: 'Fixture-provided Sidekick summary',
48+
template_owned: true,
49+
})
50+
expect(parsed.build).toEqual({include_config_on_deploy: true})
51+
})
52+
53+
test('valid app fixture can merge into generated template TOML', () => {
54+
const generatedToml = `
55+
client_id = "generated-client-id"
56+
name = "Generated app name"
57+
58+
[sidekick]
59+
extensions_summary = "Template-provided Sidekick summary"
60+
template_owned = true
61+
62+
[template_owned]
63+
kept = true
64+
`.trimStart()
65+
66+
const mergedToml = mergeFixtureToml(generatedToml, VALID_APP_FIXTURE, 'E2E valid fixture app')
67+
const parsed = toml.parse(mergedToml)
68+
69+
expect(parsed.client_id).toBe('generated-client-id')
70+
expect(parsed.name).toBe('E2E valid fixture app')
71+
expect(parsed.template_owned).toEqual({kept: true})
72+
expect(parsed.sidekick).toEqual({
73+
extensions_summary: 'Read, create, and edit FAQ entries stored in the app',
74+
template_owned: true,
75+
})
76+
expect(parsed.webhooks).toMatchObject({api_version: '2025-01'})
77+
expect((parsed.webhooks as {subscriptions: unknown[]}).subscriptions).toHaveLength(2)
78+
})
79+
})

0 commit comments

Comments
 (0)