Skip to content

Commit 172baa1

Browse files
committed
Preserve generated TOML fields in E2E fixtures
1 parent df9c10e commit 172baa1

2 files changed

Lines changed: 103 additions & 10 deletions

File tree

packages/e2e/setup/app.ts

Lines changed: 35 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,43 @@ 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+
fixture.client_id = clientId
142+
fixture.name = name
143+
144+
return toml.stringify(mergeTomlTables(generated, fixture))
145+
}
146+
119147
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)
148+
const tomlPath = path.join(appDir, 'shopify.app.toml')
149+
const patched = mergeFixtureToml(fs.readFileSync(tomlPath, 'utf8'), fixtureTomlContent, name)
150+
fs.writeFileSync(tomlPath, patched)
126151
}
127152

128153
export async function generateExtension(
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
`.trimStart()
25+
26+
const fixtureToml = `
27+
client_id = "placeholder"
28+
name = "placeholder"
29+
application_url = "https://fixture.example.com"
30+
31+
[build]
32+
include_config_on_deploy = true
33+
`.trimStart()
34+
35+
const mergedToml = mergeFixtureToml(generatedToml, fixtureToml, 'E2E merged app')
36+
const parsed = toml.parse(mergedToml)
37+
38+
expect(parsed.client_id).toBe('generated-client-id')
39+
expect(parsed.name).toBe('E2E merged app')
40+
expect(parsed.application_url).toBe('https://fixture.example.com')
41+
expect(parsed.access_scopes).toEqual({scopes: 'read_products'})
42+
expect(parsed.sidekick).toEqual({extensions_summary: 'Template-provided Sidekick summary'})
43+
expect(parsed.build).toEqual({include_config_on_deploy: true})
44+
})
45+
46+
test('valid app fixture can merge into generated template TOML', () => {
47+
const generatedToml = `
48+
client_id = "generated-client-id"
49+
name = "Generated app name"
50+
51+
[sidekick]
52+
extensions_summary = "Template-provided Sidekick summary"
53+
54+
[template_owned]
55+
kept = true
56+
`.trimStart()
57+
58+
const mergedToml = mergeFixtureToml(generatedToml, VALID_APP_FIXTURE, 'E2E valid fixture app')
59+
const parsed = toml.parse(mergedToml)
60+
61+
expect(parsed.client_id).toBe('generated-client-id')
62+
expect(parsed.name).toBe('E2E valid fixture app')
63+
expect(parsed.template_owned).toEqual({kept: true})
64+
expect(parsed.sidekick).toEqual({extensions_summary: 'Read, create, and edit FAQ entries stored in the app'})
65+
expect(parsed.webhooks).toMatchObject({api_version: '2025-01'})
66+
expect((parsed.webhooks as {subscriptions: unknown[]}).subscriptions).toHaveLength(2)
67+
})
68+
})

0 commit comments

Comments
 (0)