diff --git a/configurations/vite.config.ts b/configurations/vite.config.ts index c7fc06c8030..c6faa473af7 100644 --- a/configurations/vite.config.ts +++ b/configurations/vite.config.ts @@ -13,7 +13,7 @@ interface ConfigOptions { poolStrategy: 'threads' | 'forks' } -export default function config(packagePath: string, {poolStrategy}: ConfigOptions = {poolStrategy: 'forks'}) { +export default function config(packagePath: string, {poolStrategy}: ConfigOptions = {poolStrategy: 'threads'}) { // always treat environment as one that doesn't support hyperlinks -- otherwise assertions are hard to keep consistent process.env.FORCE_HYPERLINK = '0' process.env.FORCE_COLOR = '1' @@ -28,6 +28,11 @@ export default function config(packagePath: string, {poolStrategy}: ConfigOption } return defineConfig({ + define: { + 'process.env.SHOPIFY_UNIT_TEST': '"1"', + 'process.env.FORCE_HYPERLINK': '"0"', + 'process.env.FORCE_COLOR': '"1"', + }, resolve: { alias: aliases(packagePath) as AliasOptions, }, @@ -45,10 +50,6 @@ export default function config(packagePath: string, {poolStrategy}: ConfigOption reporter: ['text', 'json', 'lcov'], exclude: ['**/src/**/vendor/**'], }, - snapshotFormat: { - escapeString: true, - }, - includeSource: ['**/src/**/*.{ts,tsx}'], sequence: { hooks: 'list', }, @@ -58,8 +59,6 @@ export default function config(packagePath: string, {poolStrategy}: ConfigOption 'clearTimeout', 'setInterval', 'clearInterval', - 'setImmediate', - 'clearImmediate', 'Date', ], }, diff --git a/packages/app/src/cli/services/app-logs/logs-command/ui/components/Logs.test.tsx b/packages/app/src/cli/services/app-logs/logs-command/ui/components/Logs.test.tsx index bdaaa07de05..ea7795a9f1b 100644 --- a/packages/app/src/cli/services/app-logs/logs-command/ui/components/Logs.test.tsx +++ b/packages/app/src/cli/services/app-logs/logs-command/ui/components/Logs.test.tsx @@ -124,29 +124,29 @@ describe('Logs', () => { const lastFrame = renderInstance.lastFrame() expect(unstyled(lastFrame!)).toMatchInlineSnapshot(` - "2024-06-18 16:02:04.868 my-store my-function Success export \\"run\\" executed in 0.5124M instructions - test logs + "2024-06-18 16:02:04.868 my-store my-function Success export "run" executed in 0.5124M instructions + test logs - Input Query Variables: + Input Query Variables: - Namespace: inputQueryVariablesMetafieldNamespace - Key: inputQueryVariablesMetafieldKey + Namespace: inputQueryVariablesMetafieldNamespace + Key: inputQueryVariablesMetafieldKey - { - \\"key\\": \\"value\\" - } + { + "key": "value" + } - Input (10 bytes): + Input (10 bytes): - { - \\"test\\": \\"input\\" - } + { + "test": "input" + } - Output (10 bytes): + Output (10 bytes): - { - \\"test\\": \\"output\\" - }" + { + "test": "output" + }" `) renderInstance.unmount() @@ -174,7 +174,7 @@ describe('Logs', () => { const lastFrame = renderInstance.lastFrame() expect(unstyled(lastFrame!)).toMatchInlineSnapshot(` - "2024-06-18 16:02:04.868 my-store my-function Success export \\"run\\" executed in 0.5124M instructions + "2024-06-18 16:02:04.868 my-store my-function Success export "run" executed in 0.5124M instructions test logs Input Query Variables: @@ -187,15 +187,15 @@ describe('Logs', () => { Input (10 bytes): { - \\"test\\": \\"input\\" + "test": "input" } Output (10 bytes): { - \\"test\\": \\"output\\" + "test": "output" }" - `) + `) renderInstance.unmount() }) @@ -225,20 +225,20 @@ describe('Logs', () => { const lastFrame = renderInstance.lastFrame() expect(unstyled(lastFrame!)).toMatchInlineSnapshot(` - "2024-06-18 16:02:04.868 my-store my-function Success export \\"run\\" executed in 0.5124M instructions - test logs + "2024-06-18 16:02:04.868 my-store my-function Success export "run" executed in 0.5124M instructions + test logs - Input (10 bytes): + Input (10 bytes): - { - \\"test\\": \\"input\\" - } + { + "test": "input" + } - Output (10 bytes): + Output (10 bytes): - { - \\"test\\": \\"output\\" - }" + { + "test": "output" + }" `) renderInstance.unmount() @@ -280,27 +280,27 @@ describe('Logs', () => { const lastFrame = renderInstance.lastFrame() expect(unstyled(lastFrame!)).toMatchInlineSnapshot(` - "2024-06-18 16:02:04.868 my-store my-function Success network access response from cache - Cache write time: 2023-05-12T15:17:01.000Z - Cache TTL: 300 s - HTTP request: - { - \\"url\\": \\"https://api.example.com/hello\\", - \\"method\\": \\"GET\\", - \\"headers\\": {}, - \\"body\\": null, - \\"policy\\": { - \\"read_timeout_ms\\": 500 - } - } - HTTP response: - { - \\"status\\": 200, - \\"body\\": \\"Success\\", - \\"headers\\": { - \\"header1\\": \\"value1\\" + "2024-06-18 16:02:04.868 my-store my-function Success network access response from cache + Cache write time: 2023-05-12T15:17:01.000Z + Cache TTL: 300 s + HTTP request: + { + "url": "https://api.example.com/hello", + "method": "GET", + "headers": {}, + "body": null, + "policy": { + "read_timeout_ms": 500 + } } - }" + HTTP response: + { + "status": 200, + "body": "Success", + "headers": { + "header1": "value1" + } + }" `) renderInstance.unmount() @@ -342,28 +342,28 @@ describe('Logs', () => { const lastFrame = renderInstance.lastFrame() expect(unstyled(lastFrame!)).toMatchInlineSnapshot(` - "2024-06-18 16:02:04.868 my-store my-function Success network access request executed in 80 ms - Attempt: 1 - Connect time: 40 ms - Write read time: 40 ms - HTTP request: - { - \\"url\\": \\"https://api.example.com/hello\\", - \\"method\\": \\"GET\\", - \\"headers\\": {}, - \\"body\\": null, - \\"policy\\": { - \\"read_timeout_ms\\": 500 + "2024-06-18 16:02:04.868 my-store my-function Success network access request executed in 80 ms + Attempt: 1 + Connect time: 40 ms + Write read time: 40 ms + HTTP request: + { + "url": "https://api.example.com/hello", + "method": "GET", + "headers": {}, + "body": null, + "policy": { + "read_timeout_ms": 500 + } } - } - HTTP response: - { - \\"status\\": 200, - \\"body\\": \\"Success\\", - \\"headers\\": { - \\"header1\\": \\"value1\\" - } - }" + HTTP response: + { + "status": 200, + "body": "Success", + "headers": { + "header1": "value1" + } + }" `) renderInstance.unmount() @@ -405,19 +405,19 @@ describe('Logs', () => { const lastFrame = renderInstance.lastFrame() expect(unstyled(lastFrame!)).toMatchInlineSnapshot(` - "2024-06-18 16:02:04.868 my-store my-function Failure network access request executed - Attempt: 1 - HTTP request: - { - \\"url\\": \\"https://api.example.com/hello\\", - \\"method\\": \\"GET\\", - \\"headers\\": {}, - \\"body\\": null, - \\"policy\\": { - \\"read_timeout_ms\\": 500 + "2024-06-18 16:02:04.868 my-store my-function Failure network access request executed + Attempt: 1 + HTTP request: + { + "url": "https://api.example.com/hello", + "method": "GET", + "headers": {}, + "body": null, + "policy": { + "read_timeout_ms": 500 + } } - } - Error: Timeout Error" + Error: Timeout Error" `) renderInstance.unmount() @@ -455,18 +455,18 @@ describe('Logs', () => { const lastFrame = renderInstance.lastFrame() expect(unstyled(lastFrame!)).toMatchInlineSnapshot(` - "2024-06-18 16:02:04.868 my-store my-function Success network access request executing in background - Reason: No cached response available - HTTP request: - { - \\"url\\": \\"https://api.example.com/hello\\", - \\"method\\": \\"GET\\", - \\"headers\\": {}, - \\"body\\": null, - \\"policy\\": { - \\"read_timeout_ms\\": 500 - } - }" + "2024-06-18 16:02:04.868 my-store my-function Success network access request executing in background + Reason: No cached response available + HTTP request: + { + "url": "https://api.example.com/hello", + "method": "GET", + "headers": {}, + "body": null, + "policy": { + "read_timeout_ms": 500 + } + }" `) renderInstance.unmount() @@ -504,18 +504,18 @@ describe('Logs', () => { const lastFrame = renderInstance.lastFrame() expect(unstyled(lastFrame!)).toMatchInlineSnapshot(` - "2024-06-18 16:02:04.868 my-store my-function Success network access request executing in background - Reason: Cache is about to expire - HTTP request: - { - \\"url\\": \\"https://api.example.com/hello\\", - \\"method\\": \\"GET\\", - \\"headers\\": {}, - \\"body\\": null, - \\"policy\\": { - \\"read_timeout_ms\\": 500 - } - }" + "2024-06-18 16:02:04.868 my-store my-function Success network access request executing in background + Reason: Cache is about to expire + HTTP request: + { + "url": "https://api.example.com/hello", + "method": "GET", + "headers": {}, + "body": null, + "policy": { + "read_timeout_ms": 500 + } + }" `) renderInstance.unmount() diff --git a/packages/app/src/cli/services/app/__snapshots__/config-pipeline-snapshot.test.ts.snap b/packages/app/src/cli/services/app/__snapshots__/config-pipeline-snapshot.test.ts.snap index 4117ad56e9b..1c8d26f8c9c 100644 --- a/packages/app/src/cli/services/app/__snapshots__/config-pipeline-snapshot.test.ts.snap +++ b/packages/app/src/cli/services/app/__snapshots__/config-pipeline-snapshot.test.ts.snap @@ -3,363 +3,363 @@ exports[`Config pipeline snapshots > config round-trip (write → read → write) reorders webhook subscriptions 1`] = ` "# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration -client_id = \\"12345\\" -name = \\"My Test App\\" -application_url = \\"https://myapp.example.com\\" +client_id = "12345" +name = "My Test App" +application_url = "https://myapp.example.com" embedded = true [build] automatically_update_urls_on_dev = true -dev_store_url = \\"test-store.myshopify.com\\" +dev_store_url = "test-store.myshopify.com" include_config_on_deploy = true [access.admin] -direct_api_mode = \\"online\\" +direct_api_mode = "online" embedded_app_direct_api_access = true [access_scopes] # Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes -scopes = \\"read_products,write_orders\\" -required_scopes = [ \\"read_products\\" ] -optional_scopes = [ \\"write_orders\\" ] +scopes = "read_products,write_orders" +required_scopes = [ "read_products" ] +optional_scopes = [ "write_orders" ] use_legacy_install_flow = false [auth] redirect_urls = [ - \\"https://myapp.example.com/auth/callback\\", - \\"https://myapp.example.com/auth/shopify/callback\\" + "https://myapp.example.com/auth/callback", + "https://myapp.example.com/auth/shopify/callback" ] [webhooks] -api_version = \\"2024-01\\" +api_version = "2024-01" [[webhooks.subscriptions]] - uri = \\"/webhooks/compliance\\" - compliance_topics = [ \\"customers/data_request\\", \\"customers/redact\\" ] + uri = "/webhooks/compliance" + compliance_topics = [ "customers/data_request", "customers/redact" ] [[webhooks.subscriptions]] - topics = [ \\"orders/create\\" ] - uri = \\"/webhooks/orders\\" + topics = [ "orders/create" ] + uri = "/webhooks/orders" [[webhooks.subscriptions]] - topics = [ \\"products/create\\", \\"products/update\\" ] - uri = \\"/webhooks/products\\" + topics = [ "products/create", "products/update" ] + uri = "/webhooks/products" [app_proxy] -url = \\"https://myapp.example.com/proxy\\" -subpath = \\"app\\" -prefix = \\"apps\\" +url = "https://myapp.example.com/proxy" +subpath = "app" +prefix = "apps" [pos] embedded = false [app_preferences] -url = \\"https://myapp.example.com/preferences\\" +url = "https://myapp.example.com/preferences" " `; exports[`Config pipeline snapshots > minimal config without webhooks produces stable output 1`] = ` "# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration -client_id = \\"12345\\" -name = \\"Minimal App\\" -application_url = \\"https://example.com\\" +client_id = "12345" +name = "Minimal App" +application_url = "https://example.com" embedded = true [access_scopes] # Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes -scopes = \\"read_products\\" +scopes = "read_products" [auth] -redirect_urls = [ \\"https://example.com/auth/callback\\" ] +redirect_urls = [ "https://example.com/auth/callback" ] " `; exports[`Config pipeline snapshots > subscription with both topics and compliance_topics on same URI splits after round-trip 1`] = ` "# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration -client_id = \\"12345\\" -name = \\"My Test App\\" -application_url = \\"https://myapp.example.com\\" +client_id = "12345" +name = "My Test App" +application_url = "https://myapp.example.com" embedded = true [build] automatically_update_urls_on_dev = true -dev_store_url = \\"test-store.myshopify.com\\" +dev_store_url = "test-store.myshopify.com" include_config_on_deploy = true [access.admin] -direct_api_mode = \\"online\\" +direct_api_mode = "online" embedded_app_direct_api_access = true [access_scopes] # Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes -scopes = \\"read_products,write_orders\\" -required_scopes = [ \\"read_products\\" ] -optional_scopes = [ \\"write_orders\\" ] +scopes = "read_products,write_orders" +required_scopes = [ "read_products" ] +optional_scopes = [ "write_orders" ] use_legacy_install_flow = false [auth] redirect_urls = [ - \\"https://myapp.example.com/auth/callback\\", - \\"https://myapp.example.com/auth/shopify/callback\\" + "https://myapp.example.com/auth/callback", + "https://myapp.example.com/auth/shopify/callback" ] [webhooks] -api_version = \\"2024-01\\" +api_version = "2024-01" [[webhooks.subscriptions]] - topics = [ \\"orders/create\\" ] - uri = \\"/webhooks\\" - compliance_topics = [ \\"customers/data_request\\", \\"customers/redact\\" ] + topics = [ "orders/create" ] + uri = "/webhooks" + compliance_topics = [ "customers/data_request", "customers/redact" ] [app_proxy] -url = \\"https://myapp.example.com/proxy\\" -subpath = \\"app\\" -prefix = \\"apps\\" +url = "https://myapp.example.com/proxy" +subpath = "app" +prefix = "apps" [pos] embedded = false [app_preferences] -url = \\"https://myapp.example.com/preferences\\" +url = "https://myapp.example.com/preferences" " `; exports[`Config pipeline snapshots > subscriptions with same URI but different filters stay separate through round-trip 1`] = ` "# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration -client_id = \\"12345\\" -name = \\"My Test App\\" -application_url = \\"https://myapp.example.com\\" +client_id = "12345" +name = "My Test App" +application_url = "https://myapp.example.com" embedded = true [build] automatically_update_urls_on_dev = true -dev_store_url = \\"test-store.myshopify.com\\" +dev_store_url = "test-store.myshopify.com" include_config_on_deploy = true [access.admin] -direct_api_mode = \\"online\\" +direct_api_mode = "online" embedded_app_direct_api_access = true [access_scopes] # Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes -scopes = \\"read_products,write_orders\\" -required_scopes = [ \\"read_products\\" ] -optional_scopes = [ \\"write_orders\\" ] +scopes = "read_products,write_orders" +required_scopes = [ "read_products" ] +optional_scopes = [ "write_orders" ] use_legacy_install_flow = false [auth] redirect_urls = [ - \\"https://myapp.example.com/auth/callback\\", - \\"https://myapp.example.com/auth/shopify/callback\\" + "https://myapp.example.com/auth/callback", + "https://myapp.example.com/auth/shopify/callback" ] [webhooks] -api_version = \\"2024-01\\" +api_version = "2024-01" [[webhooks.subscriptions]] - topics = [ \\"orders/create\\" ] - uri = \\"/webhooks/orders\\" - filter = \\"status:paid\\" + topics = [ "orders/create" ] + uri = "/webhooks/orders" + filter = "status:paid" [[webhooks.subscriptions]] - topics = [ \\"orders/update\\" ] - uri = \\"/webhooks/orders\\" - filter = \\"status:pending\\" + topics = [ "orders/update" ] + uri = "/webhooks/orders" + filter = "status:pending" [app_proxy] -url = \\"https://myapp.example.com/proxy\\" -subpath = \\"app\\" -prefix = \\"apps\\" +url = "https://myapp.example.com/proxy" +subpath = "app" +prefix = "apps" [pos] embedded = false [app_preferences] -url = \\"https://myapp.example.com/preferences\\" +url = "https://myapp.example.com/preferences" " `; exports[`Config pipeline snapshots > subscriptions with same URI but different include_fields stay separate through round-trip 1`] = ` "# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration -client_id = \\"12345\\" -name = \\"My Test App\\" -application_url = \\"https://myapp.example.com\\" +client_id = "12345" +name = "My Test App" +application_url = "https://myapp.example.com" embedded = true [build] automatically_update_urls_on_dev = true -dev_store_url = \\"test-store.myshopify.com\\" +dev_store_url = "test-store.myshopify.com" include_config_on_deploy = true [access.admin] -direct_api_mode = \\"online\\" +direct_api_mode = "online" embedded_app_direct_api_access = true [access_scopes] # Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes -scopes = \\"read_products,write_orders\\" -required_scopes = [ \\"read_products\\" ] -optional_scopes = [ \\"write_orders\\" ] +scopes = "read_products,write_orders" +required_scopes = [ "read_products" ] +optional_scopes = [ "write_orders" ] use_legacy_install_flow = false [auth] redirect_urls = [ - \\"https://myapp.example.com/auth/callback\\", - \\"https://myapp.example.com/auth/shopify/callback\\" + "https://myapp.example.com/auth/callback", + "https://myapp.example.com/auth/shopify/callback" ] [webhooks] -api_version = \\"2024-01\\" +api_version = "2024-01" [[webhooks.subscriptions]] - topics = [ \\"products/create\\" ] - uri = \\"/webhooks/products\\" - include_fields = [ \\"id\\", \\"title\\" ] + topics = [ "products/create" ] + uri = "/webhooks/products" + include_fields = [ "id", "title" ] [[webhooks.subscriptions]] - topics = [ \\"products/update\\" ] - uri = \\"/webhooks/products\\" - include_fields = [ \\"id\\" ] + topics = [ "products/update" ] + uri = "/webhooks/products" + include_fields = [ "id" ] [app_proxy] -url = \\"https://myapp.example.com/proxy\\" -subpath = \\"app\\" -prefix = \\"apps\\" +url = "https://myapp.example.com/proxy" +subpath = "app" +prefix = "apps" [pos] embedded = false [app_preferences] -url = \\"https://myapp.example.com/preferences\\" +url = "https://myapp.example.com/preferences" " `; exports[`Config pipeline snapshots > webhook subscriptions with mixed topics and compliance topics produce stable output 1`] = ` "# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration -client_id = \\"12345\\" -name = \\"My Test App\\" -application_url = \\"https://myapp.example.com\\" +client_id = "12345" +name = "My Test App" +application_url = "https://myapp.example.com" embedded = true [build] automatically_update_urls_on_dev = true -dev_store_url = \\"test-store.myshopify.com\\" +dev_store_url = "test-store.myshopify.com" include_config_on_deploy = true [access.admin] -direct_api_mode = \\"online\\" +direct_api_mode = "online" embedded_app_direct_api_access = true [access_scopes] # Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes -scopes = \\"read_products,write_orders\\" -required_scopes = [ \\"read_products\\" ] -optional_scopes = [ \\"write_orders\\" ] +scopes = "read_products,write_orders" +required_scopes = [ "read_products" ] +optional_scopes = [ "write_orders" ] use_legacy_install_flow = false [auth] redirect_urls = [ - \\"https://myapp.example.com/auth/callback\\", - \\"https://myapp.example.com/auth/shopify/callback\\" + "https://myapp.example.com/auth/callback", + "https://myapp.example.com/auth/shopify/callback" ] [webhooks] -api_version = \\"2024-01\\" +api_version = "2024-01" [[webhooks.subscriptions]] - topics = [ \\"orders/create\\", \\"orders/updated\\", \\"orders/cancelled\\" ] - uri = \\"/webhooks/orders\\" + topics = [ "orders/create", "orders/updated", "orders/cancelled" ] + uri = "/webhooks/orders" [[webhooks.subscriptions]] - topics = [ \\"products/create\\" ] - uri = \\"/webhooks/products\\" - include_fields = [ \\"id\\", \\"title\\" ] + topics = [ "products/create" ] + uri = "/webhooks/products" + include_fields = [ "id", "title" ] [[webhooks.subscriptions]] - uri = \\"/webhooks/compliance\\" - compliance_topics = [ \\"customers/data_request\\", \\"customers/redact\\", \\"shop/redact\\" ] + uri = "/webhooks/compliance" + compliance_topics = [ "customers/data_request", "customers/redact", "shop/redact" ] [[webhooks.subscriptions]] - topics = [ \\"app/uninstalled\\" ] - uri = \\"/webhooks/app\\" + topics = [ "app/uninstalled" ] + uri = "/webhooks/app" [app_proxy] -url = \\"https://myapp.example.com/proxy\\" -subpath = \\"app\\" -prefix = \\"apps\\" +url = "https://myapp.example.com/proxy" +subpath = "app" +prefix = "apps" [pos] embedded = false [app_preferences] -url = \\"https://myapp.example.com/preferences\\" +url = "https://myapp.example.com/preferences" " `; exports[`Config pipeline snapshots > webhook subscriptions with mixed topics and compliance topics produce stable output 2`] = ` "# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration -client_id = \\"12345\\" -name = \\"My Test App\\" -application_url = \\"https://myapp.example.com\\" +client_id = "12345" +name = "My Test App" +application_url = "https://myapp.example.com" embedded = true [build] automatically_update_urls_on_dev = true -dev_store_url = \\"test-store.myshopify.com\\" +dev_store_url = "test-store.myshopify.com" include_config_on_deploy = true [access.admin] -direct_api_mode = \\"online\\" +direct_api_mode = "online" embedded_app_direct_api_access = true [access_scopes] # Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes -scopes = \\"read_products,write_orders\\" -required_scopes = [ \\"read_products\\" ] -optional_scopes = [ \\"write_orders\\" ] +scopes = "read_products,write_orders" +required_scopes = [ "read_products" ] +optional_scopes = [ "write_orders" ] use_legacy_install_flow = false [auth] redirect_urls = [ - \\"https://myapp.example.com/auth/callback\\", - \\"https://myapp.example.com/auth/shopify/callback\\" + "https://myapp.example.com/auth/callback", + "https://myapp.example.com/auth/shopify/callback" ] [webhooks] -api_version = \\"2024-01\\" +api_version = "2024-01" [[webhooks.subscriptions]] - uri = \\"/webhooks/compliance\\" - compliance_topics = [ \\"customers/data_request\\", \\"customers/redact\\", \\"shop/redact\\" ] + uri = "/webhooks/compliance" + compliance_topics = [ "customers/data_request", "customers/redact", "shop/redact" ] [[webhooks.subscriptions]] - topics = [ \\"app/uninstalled\\" ] - uri = \\"/webhooks/app\\" + topics = [ "app/uninstalled" ] + uri = "/webhooks/app" [[webhooks.subscriptions]] - topics = [ \\"orders/cancelled\\", \\"orders/create\\", \\"orders/updated\\" ] - uri = \\"/webhooks/orders\\" + topics = [ "orders/cancelled", "orders/create", "orders/updated" ] + uri = "/webhooks/orders" [[webhooks.subscriptions]] - topics = [ \\"products/create\\" ] - uri = \\"/webhooks/products\\" - include_fields = [ \\"id\\", \\"title\\" ] + topics = [ "products/create" ] + uri = "/webhooks/products" + include_fields = [ "id", "title" ] [app_proxy] -url = \\"https://myapp.example.com/proxy\\" -subpath = \\"app\\" -prefix = \\"apps\\" +url = "https://myapp.example.com/proxy" +subpath = "app" +prefix = "apps" [pos] embedded = false [app_preferences] -url = \\"https://myapp.example.com/preferences\\" +url = "https://myapp.example.com/preferences" " `; diff --git a/packages/app/src/cli/services/dev/app-events/app-event-watcher.test.ts b/packages/app/src/cli/services/dev/app-events/app-event-watcher.test.ts index 3bdb24b3f91..cc867334e4e 100644 --- a/packages/app/src/cli/services/dev/app-events/app-event-watcher.test.ts +++ b/packages/app/src/cli/services/dev/app-events/app-event-watcher.test.ts @@ -369,7 +369,7 @@ describe('app-event-watcher', () => { await flushPromises() // Wait for event processing - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) // Then expect(generateTypesSpy).toHaveBeenCalled() @@ -405,7 +405,7 @@ describe('app-event-watcher', () => { await flushPromises() // Wait for event processing - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) // Then - not called in watcher because it was already called during reloadApp expect(generateTypesSpy).not.toHaveBeenCalled() @@ -441,7 +441,7 @@ describe('app-event-watcher', () => { await flushPromises() // Wait for event processing - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) // Then - not called in watcher because it was already called during reloadApp expect(generateTypesSpy).not.toHaveBeenCalled() @@ -474,7 +474,7 @@ describe('app-event-watcher', () => { await flushPromises() // Wait for event processing - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) // Then - generateExtensionTypes should still be called when extensions are deleted // to clean up type definitions for the removed extension diff --git a/packages/app/src/cli/services/dev/app-events/file-watcher.test.ts b/packages/app/src/cli/services/dev/app-events/file-watcher.test.ts index d28858226b7..a64604f6cc1 100644 --- a/packages/app/src/cli/services/dev/app-events/file-watcher.test.ts +++ b/packages/app/src/cli/services/dev/app-events/file-watcher.test.ts @@ -707,7 +707,7 @@ describe('file-watcher events', () => { } // Wait for debounced events - await new Promise((resolve) => setTimeout(resolve, 300)) + await new Promise((resolve) => setTimeout(resolve, 30)) // Test passes if we reach here without hanging clearTimeout(timeout) diff --git a/packages/app/src/cli/services/dev/ui.test.tsx b/packages/app/src/cli/services/dev/ui.test.tsx index f41ff142733..587d26a9564 100644 --- a/packages/app/src/cli/services/dev/ui.test.tsx +++ b/packages/app/src/cli/services/dev/ui.test.tsx @@ -189,7 +189,7 @@ describe('ui', () => { devSessionStatusManager, }) - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) expect(vi.mocked(Dev)).toHaveBeenCalled() expect(concurrentProcess.action).not.toHaveBeenCalled() @@ -235,7 +235,7 @@ describe('ui', () => { devSessionStatusManager, }) - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) expect(vi.mocked(DevSessionUI)).toHaveBeenCalledWith( expect.objectContaining({ @@ -286,7 +286,7 @@ describe('ui', () => { devSessionStatusManager, }) - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) // Get the onAbort callback that was passed to DevSessionUI const onAbort = vi.mocked(DevSessionUI).mock.calls[0]?.[0]?.onAbort diff --git a/packages/app/src/cli/services/dev/ui/components/Dev.test.tsx b/packages/app/src/cli/services/dev/ui/components/Dev.test.tsx index 9755e87bd12..77b040f128f 100644 --- a/packages/app/src/cli/services/dev/ui/components/Dev.test.tsx +++ b/packages/app/src/cli/services/dev/ui/components/Dev.test.tsx @@ -222,7 +222,7 @@ describe('Dev', () => { ) await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, 'p') + await sendInputAndWait(renderInstance, 10, 'p') // Then expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, 'https://shopify.com') @@ -280,7 +280,7 @@ describe('Dev', () => { const promise = renderInstance.waitUntilExit() await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, '\u0003') + await sendInputAndWait(renderInstance, 10, '\u0003') await promise // Then @@ -439,7 +439,7 @@ describe('Dev', () => { `) await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, 'p') + await sendInputAndWait(renderInstance, 10, 'p') expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, 'https://shopify.com') // unmount so that polling is cleared after every test @@ -503,7 +503,7 @@ describe('Dev', () => { // Then // Wait long enough for multiple polling cycles - await sleep(0.1) + await sleep(0.05) // enable should be called once at startup expect(developerPreview.enable).toHaveBeenCalledTimes(1) @@ -550,7 +550,7 @@ describe('Dev', () => { // Then // Wait long enough for multiple polling cycles - await sleep(0.1) + await sleep(0.05) expect(unstyled(renderInstance.lastFrame()!).replace(/\d/g, '0')).toMatchInlineSnapshot(` "00:00:00 │ backend │ first backend message 00:00:00 │ backend │ second backend message @@ -571,7 +571,7 @@ describe('Dev', () => { // Verify 'd' input doesn't trigger update when app doesn't support preview await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, 'd') + await sendInputAndWait(renderInstance, 10, 'd') expect(developerPreview.update).not.toHaveBeenCalled() // unmount so that polling is cleared after every test @@ -597,7 +597,7 @@ describe('Dev', () => { />, ) - await waitForInputsToBeReady() + await waitForContent(renderInstance, 'Failed to fetch') expect(unstyled(renderInstance.lastFrame()!).replace(/\d\d:\d\d:\d\d/g, '00:00:00')).toMatchInlineSnapshot(` " @@ -652,7 +652,7 @@ describe('Dev', () => { `) await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, 'd') + await sendInputAndWait(renderInstance, 10, 'd') expect(developerPreview.update).toHaveBeenCalledOnce() await waitForContent(renderInstance, 'off') diff --git a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx index b70b20d2676..ac93a0479fc 100644 --- a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx +++ b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx @@ -142,7 +142,7 @@ describe('DevSessionUI', () => { ) await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, 'p') + await sendInputAndWait(renderInstance, 10, 'p') // Then expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, 'https://shopify.com') @@ -163,7 +163,7 @@ describe('DevSessionUI', () => { ) await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, 'g') + await sendInputAndWait(renderInstance, 10, 'g') // Then expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, 'https://graphiql.shopify.com') @@ -455,7 +455,7 @@ describe('DevSessionUI', () => { await waitForInputsToBeReady() // When - await sendInputAndWait(renderInstance, 100, 'a') + await sendInputAndWait(renderInstance, 10, 'a') // Then - info tab should be shown with app data const output = renderInstance.lastFrame()! diff --git a/packages/app/src/cli/services/dev/ui/components/TabPanel.test.tsx b/packages/app/src/cli/services/dev/ui/components/TabPanel.test.tsx index 8e0038e61a2..9ed7aff0e7f 100644 --- a/packages/app/src/cli/services/dev/ui/components/TabPanel.test.tsx +++ b/packages/app/src/cli/services/dev/ui/components/TabPanel.test.tsx @@ -1,5 +1,10 @@ import {TabPanel, Tab} from './TabPanel.js' -import {render, sendInputAndWait, waitForInputsToBeReady} from '@shopify/cli-kit/node/testing/ui' +import { + render, + sendInputAndWait, + sendInputAndWaitForChange, + waitForInputsToBeReady, +} from '@shopify/cli-kit/node/testing/ui' import React from 'react' import {describe, expect, test, vi} from 'vitest' import {unstyled} from '@shopify/cli-kit/node/output' @@ -96,7 +101,7 @@ describe('TabPanel', () => { expect(renderInstance.lastFrame()!).toContain('First tab content') // Press 'b' to switch to second tab - await sendInputAndWait(renderInstance, 100, 'b') + await sendInputAndWaitForChange(renderInstance, 'b') expect(renderInstance.lastFrame()!).toContain('Second tab content') expect(renderInstance.lastFrame()!).not.toContain('First tab content') @@ -110,7 +115,7 @@ describe('TabPanel', () => { await waitForInputsToBeReady() // Press 'c' to trigger action tab - await sendInputAndWait(renderInstance, 100, 'c') + await sendInputAndWait(renderInstance, 10, 'c') expect(mockAction).toHaveBeenCalledOnce() @@ -123,7 +128,7 @@ describe('TabPanel', () => { await waitForInputsToBeReady() // Press 'x' to trigger shortcut in active tab - await sendInputAndWait(renderInstance, 100, 'x') + await sendInputAndWait(renderInstance, 10, 'x') expect(mockShortcutAction).toHaveBeenCalledOnce() @@ -152,12 +157,12 @@ describe('TabPanel', () => { await waitForInputsToBeReady() // Press 'y' while on tab 'a' - should not trigger second tab's shortcut - await sendInputAndWait(renderInstance, 100, 'y') + await sendInputAndWait(renderInstance, 10, 'y') expect(secondTabShortcut).not.toHaveBeenCalled() // Switch to tab 'b' and press 'y' - should trigger second tab's shortcut - await sendInputAndWait(renderInstance, 100, 'b') - await sendInputAndWait(renderInstance, 100, 'y') + await sendInputAndWaitForChange(renderInstance, 'b') + await sendInputAndWait(renderInstance, 10, 'y') expect(secondTabShortcut).toHaveBeenCalledOnce() renderInstance.unmount() @@ -194,12 +199,12 @@ describe('TabPanel', () => { await waitForInputsToBeReady() // Press 'x' - condition is met, action should execute - await sendInputAndWait(renderInstance, 100, 'x') + await sendInputAndWait(renderInstance, 10, 'x') expect(conditionMet).toHaveBeenCalled() expect(actionWhenConditionMet).toHaveBeenCalledOnce() // Press 'y' - condition is not met, action should not execute - await sendInputAndWait(renderInstance, 100, 'y') + await sendInputAndWait(renderInstance, 10, 'y') expect(conditionNotMet).toHaveBeenCalled() expect(actionWhenConditionNotMet).not.toHaveBeenCalled() @@ -216,7 +221,7 @@ describe('TabPanel', () => { expect(output).toContain('(a) First Tab') // Switch to second tab - await sendInputAndWait(renderInstance, 100, 'b') + await sendInputAndWaitForChange(renderInstance, 'b') // The display should update to show the new active tab expect(renderInstance.lastFrame()!).toContain('Second tab content') @@ -234,7 +239,7 @@ describe('TabPanel', () => { expect(renderInstance.lastFrame()!).toContain('First tab content') // Press 'b' - should not switch tabs since input is disabled - await sendInputAndWait(renderInstance, 100, 'b') + await sendInputAndWait(renderInstance, 10, 'b') expect(renderInstance.lastFrame()!).toContain('First tab content') expect(renderInstance.lastFrame()!).not.toContain('Second tab content') @@ -273,14 +278,14 @@ describe('TabPanel', () => { expect(renderInstance.lastFrame()!).not.toContain('Second tab content') // Press right arrow to navigate to next content tab (skipping action tab) - await sendInputAndWait(renderInstance, 100, '\u001B[C') + await sendInputAndWaitForChange(renderInstance, '\u001B[C') // Should now show second tab content expect(renderInstance.lastFrame()!).toContain('Second tab content') expect(renderInstance.lastFrame()!).not.toContain('First tab content') // Press left arrow to navigate back to first tab - await sendInputAndWait(renderInstance, 100, '\u001B[D') + await sendInputAndWaitForChange(renderInstance, '\u001B[D') // Should be back to first tab content expect(renderInstance.lastFrame()!).toContain('First tab content') @@ -311,22 +316,22 @@ describe('TabPanel', () => { expect(renderInstance.lastFrame()!).toContain('First tab content') // Press left arrow on leftmost tab - should loop to last tab (second tab) - await sendInputAndWait(renderInstance, 100, '\u001B[D') + await sendInputAndWaitForChange(renderInstance, '\u001B[D') expect(renderInstance.lastFrame()!).toContain('Second tab content') expect(renderInstance.lastFrame()!).not.toContain('First tab content') // Navigate back to first tab - await sendInputAndWait(renderInstance, 100, '\u001B[C') + await sendInputAndWaitForChange(renderInstance, '\u001B[C') expect(renderInstance.lastFrame()!).toContain('First tab content') expect(renderInstance.lastFrame()!).not.toContain('Second tab content') // Navigate to second tab - await sendInputAndWait(renderInstance, 100, '\u001B[C') + await sendInputAndWaitForChange(renderInstance, '\u001B[C') expect(renderInstance.lastFrame()!).toContain('Second tab content') expect(renderInstance.lastFrame()!).not.toContain('First tab content') // Press right arrow on rightmost tab - should loop to first tab - await sendInputAndWait(renderInstance, 100, '\u001B[C') + await sendInputAndWaitForChange(renderInstance, '\u001B[C') expect(renderInstance.lastFrame()!).toContain('First tab content') expect(renderInstance.lastFrame()!).not.toContain('Second tab content') @@ -360,17 +365,17 @@ describe('TabPanel', () => { expect(renderInstance.lastFrame()!).toContain('First tab content') // Press tab key to navigate to next tab - await sendInputAndWait(renderInstance, 100, '\t') + await sendInputAndWaitForChange(renderInstance, '\t') expect(renderInstance.lastFrame()!).toContain('Second tab content') expect(renderInstance.lastFrame()!).not.toContain('First tab content') // Press tab key again to navigate to third tab - await sendInputAndWait(renderInstance, 100, '\t') + await sendInputAndWaitForChange(renderInstance, '\t') expect(renderInstance.lastFrame()!).toContain('Third tab content') expect(renderInstance.lastFrame()!).not.toContain('Second tab content') // Press tab key on last tab - should loop to first tab - await sendInputAndWait(renderInstance, 100, '\t') + await sendInputAndWaitForChange(renderInstance, '\t') expect(renderInstance.lastFrame()!).toContain('First tab content') expect(renderInstance.lastFrame()!).not.toContain('Third tab content') diff --git a/packages/app/src/cli/services/function/ui/components/Replay/Replay.test.tsx b/packages/app/src/cli/services/function/ui/components/Replay/Replay.test.tsx index 44af6642b7c..35bebfd6e14 100644 --- a/packages/app/src/cli/services/function/ui/components/Replay/Replay.test.tsx +++ b/packages/app/src/cli/services/function/ui/components/Replay/Replay.test.tsx @@ -140,7 +140,7 @@ describe('Replay', () => { const promise = renderInstanceReplay.waitUntilExit() await waitForInputsToBeReady() - await sendInputAndWait(renderInstanceReplay, 100, 'q') + await sendInputAndWait(renderInstanceReplay, 10, 'q') await promise @@ -171,7 +171,7 @@ describe('Replay', () => { const promise = renderInstanceReplay.waitUntilExit() await waitForInputsToBeReady() - await sendInputAndWait(renderInstanceReplay, 100, '\u0003') + await sendInputAndWait(renderInstanceReplay, 10, '\u0003') await promise // Then diff --git a/packages/app/src/cli/services/function/ui/components/Replay/__snapshots__/Replay.test.tsx.snap b/packages/app/src/cli/services/function/ui/components/Replay/__snapshots__/Replay.test.tsx.snap index 32645071ef1..af20bb49f5c 100644 --- a/packages/app/src/cli/services/function/ui/components/Replay/__snapshots__/Replay.test.tsx.snap +++ b/packages/app/src/cli/services/function/ui/components/Replay/__snapshots__/Replay.test.tsx.snap @@ -6,7 +6,7 @@ exports[`Replay > renders a stream of lines from function-runner output, and sho Input { - \\"someInput\\": \\"someInput\\" + "someInput": "someInput" } @@ -22,7 +22,7 @@ Fifth line! Output { - \\"someOutput\\": \\"someOutput\\" + "someOutput": "someOutput" } @@ -38,7 +38,7 @@ Size: 1KB Input { - \\"someInput\\": \\"someInput\\" + "someInput": "someInput" } @@ -54,7 +54,7 @@ Fifth line! Output { - \\"someOutput\\": \\"someOutput\\" + "someOutput": "someOutput" } @@ -80,7 +80,7 @@ exports[`Replay > renders error in the bottom bar when present 1`] = ` Input { - \\"someInput\\": \\"someInput\\" + "someInput": "someInput" } @@ -96,7 +96,7 @@ Fifth line! Output { - \\"someOutput\\": \\"someOutput\\" + "someOutput": "someOutput" } @@ -112,7 +112,7 @@ Size: 1KB Input { - \\"someInput\\": \\"someInput\\" + "someInput": "someInput" } @@ -128,7 +128,7 @@ Fifth line! Output { - \\"someOutput\\": \\"someOutput\\" + "someOutput": "someOutput" } diff --git a/packages/app/src/cli/services/generate/shop-import/declarative-definitions.test.ts b/packages/app/src/cli/services/generate/shop-import/declarative-definitions.test.ts index 72151743a08..17d3a0b6b85 100644 --- a/packages/app/src/cli/services/generate/shop-import/declarative-definitions.test.ts +++ b/packages/app/src/cli/services/generate/shop-import/declarative-definitions.test.ts @@ -105,9 +105,9 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" " `) }) @@ -132,9 +132,9 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app:custom key: color owner_type: PRODUCT [product.metafields.custom.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" " `) }) @@ -184,14 +184,14 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" [product.metafields.app.color.validations] min = 5 max = 50 - choices = [\\"red\\", \\"blue\\", \\"green\\"] + choices = ["red", "blue", "green"] " `) }) @@ -220,12 +220,12 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" - access.admin = \\"merchant_read_write\\" - access.storefront = \\"public_read\\" - access.customer_account = \\"read\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" + access.admin = "merchant_read_write" + access.storefront = "public_read" + access.customer_account = "read" " `) }) @@ -254,9 +254,9 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" capabilities.admin_filterable = true " `) @@ -302,21 +302,21 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" # namespace: $app key: size owner_type: PRODUCT [product.metafields.app.size] - name = \\"Size\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Size" + type = "single_line_text_field" + description = "The color of the product" # namespace: $app key: vip_status owner_type: CUSTOMER [customer.metafields.app.vip_status] - name = \\"VIP Status\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "VIP Status" + type = "single_line_text_field" + description = "The color of the product" " `) }) @@ -336,7 +336,7 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# type: $app:test [metaobjects.app.test.fields] - foo = \\"single_line_text_field\\" + foo = "single_line_text_field" " `) }) @@ -392,21 +392,21 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# type: $app:test [metaobjects.app.test] - name = \\"A test\\" - description = \\"A test metaobject\\" - display_name_field = \\"title\\" - access.admin = \\"merchant_read_write\\" - access.storefront = \\"public_read\\" + name = "A test" + description = "A test metaobject" + display_name_field = "title" + access.admin = "merchant_read_write" + access.storefront = "public_read" capabilities.translatable = true capabilities.publishable = true capabilities.renderable = true - capabilities.renderable_meta_title_field = \\"title\\" - capabilities.renderable_meta_description_field = \\"description\\" + capabilities.renderable_meta_title_field = "title" + capabilities.renderable_meta_description_field = "description" [metaobjects.app.test.fields.title] - type = \\"single_line_text_field\\" - description = \\"The title of the test metaobject\\" - name = \\"Title\\" + type = "single_line_text_field" + description = "The title of the test metaobject" + name = "Title" required = true [metaobjects.app.test.fields.title.validations] @@ -458,13 +458,13 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# type: $app:test [metaobjects.app.test.fields] - foo = \\"single_line_text_field\\" + foo = "single_line_text_field" # namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" " `) }) @@ -490,8 +490,8 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: samename owner_type: PRODUCT [product.metafields.app.samename] - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + type = "single_line_text_field" + description = "The color of the product" " `) // Should not contain name since it equals key @@ -524,16 +524,16 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" [product.metafields.app.color.validations] - simple_string = \\"test\\" + simple_string = "test" simple_number = 42 simple_boolean = true - array_strings = [\\"a\\", \\"b\\"] - complex_object = '{\\"nested\\":{\\"value\\":123}}' + array_strings = ["a", "b"] + complex_object = '{"nested":{"value":123}}' " `) }) @@ -558,8 +558,8 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" + name = "Color" + type = "single_line_text_field" " `) expect(result.tomlContent).not.toContain('description =') @@ -585,9 +585,9 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" " `) }) @@ -618,8 +618,8 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# type: $app:test [metaobjects.app.test.fields.price_field] - type = \\"money\\" - name = \\"Price\\" + type = "money" + name = "Price" " `) }) @@ -647,9 +647,9 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" [product.metafields.app.color.validations] min = 5 @@ -676,7 +676,7 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# type: $app:test [metaobjects.app.test.fields] - foo = \\"single_line_text_field\\" + foo = "single_line_text_field" " `) }) @@ -698,7 +698,7 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# type: $app:test [metaobjects.app.test.fields] - foo = \\"single_line_text_field\\" + foo = "single_line_text_field" " `) }) @@ -741,11 +741,11 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# type: $app:test [metaobjects.app.test.fields.price_field] - type = \\"list.metaobject_reference<$app:referenced_into>\\" - name = \\"Price\\" + type = "list.metaobject_reference<$app:referenced_into>" + name = "Price" [metaobjects.app.test.fields.price_field.validations] - \\"list.max\\" = 3 + "list.max" = 3 " `) }) @@ -785,8 +785,8 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# type: $app:test [metaobjects.app.test.fields.price_field] - type = \\"mixed_reference<$app:referenced_into,$app:referenced_into2>\\" - name = \\"Price\\" + type = "mixed_reference<$app:referenced_into,$app:referenced_into2>" + name = "Price" " `) }) @@ -815,9 +815,9 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" " `) }) @@ -839,10 +839,10 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# type: $app:app [metaobjects.app.app] - name = \\"test\\" + name = "test" [metaobjects.app.app.fields] - foo = \\"single_line_text_field\\" + foo = "single_line_text_field" " `) }) @@ -874,13 +874,13 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" [product.metafields.app.color.validations] valid_json = 42 - invalid_json = \\"this is not valid json {{\\" + invalid_json = "this is not valid json {{" " `) }) @@ -911,12 +911,12 @@ describe('processDeclarativeDefinitionNodes', () => { expect(result.tomlContent).toMatchInlineSnapshot(` "# namespace: $app key: color owner_type: PRODUCT [product.metafields.app.color] - name = \\"Color\\" - type = \\"single_line_text_field\\" - description = \\"The color of the product\\" - access.admin = \\"merchant_read_write\\" - access.storefront = \\"public_read\\" - access.customer_account = \\"read_write\\" + name = "Color" + type = "single_line_text_field" + description = "The color of the product" + access.admin = "merchant_read_write" + access.storefront = "public_read" + access.customer_account = "read_write" " `) }) @@ -940,7 +940,7 @@ plain line` [ "# comment line", "[section.header]", - "key = \\"value\\"", + "key = "value"", "plain line", ] `) @@ -1350,11 +1350,11 @@ describe('importDeclarativeDefinitions', () => { # type: $app:new [metaobjects.app.new.fields] - field = \\"single_line_text_field\\" + field = "single_line_text_field" # namespace: $app key: new owner_type: PRODUCT [product.metafields.app.new] - type = \\"single_line_text_field\\" + type = "single_line_text_field" " `) outputMock.clear() @@ -1387,19 +1387,19 @@ describe('importDeclarativeDefinitions', () => { # type: $app:existing [metaobjects.app.existing.fields] - field = \\"single_line_text_field\\" + field = "single_line_text_field" # type: $app:new [metaobjects.app.new.fields] - field = \\"single_line_text_field\\" + field = "single_line_text_field" # namespace: $app key: existing owner_type: PRODUCT [product.metafields.app.existing] - type = \\"single_line_text_field\\" + type = "single_line_text_field" # namespace: $app key: new owner_type: PRODUCT [product.metafields.app.new] - type = \\"single_line_text_field\\" + type = "single_line_text_field" " `) outputMock.clear() diff --git a/packages/app/src/cli/services/info.test.ts b/packages/app/src/cli/services/info.test.ts index 9e87bb0c520..1904fb3d089 100644 --- a/packages/app/src/cli/services/info.test.ts +++ b/packages/app/src/cli/services/info.test.ts @@ -116,9 +116,9 @@ describe('info', () => { // Then expect(unstyled(stringifyMessage(result))).toMatchInlineSnapshot(` "{ - \\"SHOPIFY_API_KEY\\": \\"api-key\\", - \\"SHOPIFY_API_SECRET\\": \\"api-secret\\", - \\"SCOPES\\": \\"my-scope\\" + "SHOPIFY_API_KEY": "api-key", + "SHOPIFY_API_SECRET": "api-secret", + "SCOPES": "my-scope" }" `) }) diff --git a/packages/app/src/cli/services/versions-list.test.ts b/packages/app/src/cli/services/versions-list.test.ts index c3d2440645f..772e18ef1cd 100644 --- a/packages/app/src/cli/services/versions-list.test.ts +++ b/packages/app/src/cli/services/versions-list.test.ts @@ -200,18 +200,18 @@ View all 31 app versions in the Test Dashboard ( https://test.shopify.com/org-id expect(mockOutput.info()).toMatchInlineSnapshot(` "[ { - \\"message\\": \\"message\\", - \\"versionTag\\": \\"versionTag\\", - \\"status\\": \\"active\\", - \\"createdAt\\": \\"2021-01-01 00:00:00\\", - \\"createdBy\\": \\"createdBy\\" + "message": "message", + "versionTag": "versionTag", + "status": "active", + "createdAt": "2021-01-01 00:00:00", + "createdBy": "createdBy" }, { - \\"message\\": \\"long message with more than 15 characters\\", - \\"versionTag\\": \\"versionTag 3\\", - \\"status\\": \\"released\\", - \\"createdAt\\": \\"2021-01-01 00:00:00\\", - \\"createdBy\\": \\"createdBy 3\\" + "message": "long message with more than 15 characters", + "versionTag": "versionTag 3", + "status": "released", + "createdAt": "2021-01-01 00:00:00", + "createdBy": "createdBy 3" } ]" `) diff --git a/packages/app/src/cli/utilities/app/http-reverse-proxy.test.ts b/packages/app/src/cli/utilities/app/http-reverse-proxy.test.ts index 1dea9fe44c0..f28595c039e 100644 --- a/packages/app/src/cli/utilities/app/http-reverse-proxy.test.ts +++ b/packages/app/src/cli/utilities/app/http-reverse-proxy.test.ts @@ -59,7 +59,7 @@ describe.sequential.each(each)('http-reverse-proxy for %s', (protocol) => { // eslint-disable-next-line no-catch-all/no-catch-all } catch (error) { // If the assertion fails, wait a bit and try again - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) await expect(fetch(`${protocol}://localhost:${ports.proxyPort}/path1`, {agent})).rejects.toThrow() } }) diff --git a/packages/cli-kit/src/private/node/__snapshots__/otel-metrics.test.ts.snap b/packages/cli-kit/src/private/node/__snapshots__/otel-metrics.test.ts.snap index f50b589a761..3a1d3fdc918 100644 --- a/packages/cli-kit/src/private/node/__snapshots__/otel-metrics.test.ts.snap +++ b/packages/cli-kit/src/private/node/__snapshots__/otel-metrics.test.ts.snap @@ -55,20 +55,20 @@ exports[`otel-metrics > logs metrics when activated 1`] = ` exports[`otel-metrics > outputs debug information when deactivated 1`] = ` "[OTEL] record cli_commands_total counter { - \\"labels\\": { - \\"exit\\": \\"ok\\", - \\"job\\": \\"@shopify/app::app dev\\", - \\"cli_version\\": \\"nightly\\" + "labels": { + "exit": "ok", + "job": "@shopify/app::app dev", + "cli_version": "nightly" } } [OTEL] record cli_commands_duration_ms histogram 10ms { - \\"labels\\": { - \\"exit\\": \\"ok\\", - \\"job\\": \\"@shopify/app::app dev\\", - \\"cli_version\\": \\"nightly\\" + "labels": { + "exit": "ok", + "job": "@shopify/app::app dev", + "cli_version": "nightly" } } -[OTEL] record cli_commands_wall_clock_elapsed_ms histogram stage=\\"active\\" 10ms -[OTEL] record cli_commands_wall_clock_elapsed_ms histogram stage=\\"network\\" 20ms -[OTEL] record cli_commands_wall_clock_elapsed_ms histogram stage=\\"prompt\\" 30ms" +[OTEL] record cli_commands_wall_clock_elapsed_ms histogram stage="active" 10ms +[OTEL] record cli_commands_wall_clock_elapsed_ms histogram stage="network" 20ms +[OTEL] record cli_commands_wall_clock_elapsed_ms histogram stage="prompt" 30ms" `; diff --git a/packages/cli-kit/src/private/node/testing/ui.ts b/packages/cli-kit/src/private/node/testing/ui.ts index 5db5d37da90..05201a2fab5 100644 --- a/packages/cli-kit/src/private/node/testing/ui.ts +++ b/packages/cli-kit/src/private/node/testing/ui.ts @@ -94,7 +94,7 @@ export const render = (tree: ReactElement, options: RenderOptions = {}): Instanc * Wait for the component to be ready to accept input. */ export function waitForInputsToBeReady() { - return new Promise((resolve) => setTimeout(resolve, 100)) + return new Promise((resolve) => setTimeout(resolve, 10)) } /** diff --git a/packages/cli-kit/src/private/node/ui/components/AutocompletePrompt.test.tsx b/packages/cli-kit/src/private/node/ui/components/AutocompletePrompt.test.tsx index 8e04bb76d7c..7c449a9d710 100644 --- a/packages/cli-kit/src/private/node/ui/components/AutocompletePrompt.test.tsx +++ b/packages/cli-kit/src/private/node/ui/components/AutocompletePrompt.test.tsx @@ -287,7 +287,7 @@ describe('AutocompletePrompt', async () => { await sendInputAndWaitForContent(renderInstance, 'No results found', 'a') // prompt doesn't change when enter is pressed - await sendInputAndWait(renderInstance, 100, ENTER) + await sendInputAndWait(renderInstance, 10, ENTER) expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` "? Associate your project with the org Castile Ventures? a█ @@ -348,8 +348,8 @@ describe('AutocompletePrompt', async () => { await waitForInputsToBeReady() await sendInputAndWaitForContent(renderInstance, 'Loading...', 'a') // prompt doesn't change when enter is pressed - await new Promise((resolve) => setTimeout(resolve, 100)) - await sendInputAndWait(renderInstance, 100, ENTER) + await new Promise((resolve) => setTimeout(resolve, 10)) + await sendInputAndWait(renderInstance, 10, ENTER) expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` "? Associate your project with the org Castile Ventures? a█ @@ -587,9 +587,9 @@ describe('AutocompletePrompt', async () => { await sendInputAndWaitForContent(renderInstance, 'slash', '\\') expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` - "? Associate your project with the org Castile Ventures? \\\\█ + "? Associate your project with the org Castile Ventures? \\█ - > with\\\\slash + > with\\slash diff --git a/packages/cli-kit/src/private/node/ui/components/ConcurrentOutput.test.tsx b/packages/cli-kit/src/private/node/ui/components/ConcurrentOutput.test.tsx index a0b2d7757ae..f9d61c55556 100644 --- a/packages/cli-kit/src/private/node/ui/components/ConcurrentOutput.test.tsx +++ b/packages/cli-kit/src/private/node/ui/components/ConcurrentOutput.test.tsx @@ -310,7 +310,7 @@ describe('ConcurrentOutput', () => { />, ) - await new Promise((resolve) => setTimeout(resolve, 500)) + await new Promise((resolve) => setTimeout(resolve, 10)) expect(renderInstance.waitUntilExit().isRejected()).toBe(false) }) @@ -354,7 +354,7 @@ describe('ConcurrentOutput', () => { />, ) - await new Promise((resolve) => setTimeout(resolve, 500)) + await new Promise((resolve) => setTimeout(resolve, 10)) expect(renderInstance.waitUntilExit().isFulfilled()).toBe(false) }) diff --git a/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx b/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx index e3e2ea17eee..252fbfe17ca 100644 --- a/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx +++ b/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx @@ -153,7 +153,7 @@ describe('SelectInput', async () => { await waitForInputsToBeReady() // nothing changes when pressing a key that doesn't exist - await sendInputAndWait(renderInstance, 100, '4') + await sendInputAndWait(renderInstance, 10, '4') expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` "> First @@ -325,7 +325,7 @@ describe('SelectInput', async () => { await waitForInputsToBeReady() // input doesn't change on shortcut pressed - await sendInputAndWait(renderInstance, 100, '2') + await sendInputAndWait(renderInstance, 10, '2') expect(renderInstance.lastFrame()).toMatchInlineSnapshot(` "> First @@ -477,7 +477,7 @@ describe('SelectInput', async () => { const renderInstance = render( {}} onSubmit={onSubmit} />) await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, ENTER) + await sendInputAndWait(renderInstance, 10, ENTER) expect(onSubmit).toHaveBeenCalledWith(items[0]) }) @@ -502,8 +502,8 @@ describe('SelectInput', async () => { const renderInstance = render( {}} onSubmit={onSubmit} />) await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, ARROW_DOWN) - await sendInputAndWait(renderInstance, 100, ENTER) + await sendInputAndWaitForChange(renderInstance, ARROW_DOWN) + await sendInputAndWait(renderInstance, 10, ENTER) expect(onSubmit).toHaveBeenCalledWith(items[1]) }) @@ -531,7 +531,7 @@ describe('SelectInput', async () => { const renderInstance = render( {}} onSubmit={onSubmit} />) await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, 's') + await sendInputAndWait(renderInstance, 10, 's') expect(onSubmit).toHaveBeenCalledWith(items[1]) }) @@ -567,7 +567,7 @@ describe('SelectInput', async () => { Press ↑↓ arrows to select, enter to confirm." `) - await sendInputAndWait(renderInstance, 100, ENTER) + await sendInputAndWait(renderInstance, 10, ENTER) expect(onSubmit).toHaveBeenCalledWith(items[2]) }) @@ -603,7 +603,7 @@ describe('SelectInput', async () => { `) await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, ENTER) + await sendInputAndWait(renderInstance, 10, ENTER) expect(onSubmit).toHaveBeenCalledWith(items[0]) }) @@ -642,7 +642,7 @@ describe('SelectInput', async () => { Press ↑↓ arrows to select, enter or a shortcut to confirm." `) - await sendInputAndWait(renderInstance, 100, ENTER) + await sendInputAndWait(renderInstance, 10, ENTER) expect(onSubmit).toHaveBeenCalledWith(items[2]) }) @@ -671,7 +671,7 @@ describe('SelectInput', async () => { const renderInstance = render( {}} onSubmit={onSubmit} />) await waitForInputsToBeReady() - await sendInputAndWait(renderInstance, 100, 's') + await sendInputAndWait(renderInstance, 10, 's') expect(onSubmit).not.toHaveBeenCalled() }) diff --git a/packages/cli-kit/src/private/node/ui/components/Tasks.test.tsx b/packages/cli-kit/src/private/node/ui/components/Tasks.test.tsx index 4b785055852..7fb39869302 100644 --- a/packages/cli-kit/src/private/node/ui/components/Tasks.test.tsx +++ b/packages/cli-kit/src/private/node/ui/components/Tasks.test.tsx @@ -77,7 +77,7 @@ describe('Tasks', () => { test('it supports subtasks', async () => { // Given const firstSubtaskFunction = vi.fn(async () => { - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) }) const secondSubtaskFunction = vi.fn(async () => {}) @@ -115,11 +115,11 @@ describe('Tasks', () => { test('supports skipping', async () => { // Given const firstTaskFunction = vi.fn(async () => { - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) }) const secondTaskFunction = vi.fn(async () => { - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) }) const firstTask = { @@ -145,11 +145,11 @@ describe('Tasks', () => { test('supports skipping a subtask', async () => { // Given const firstSubTaskFunction = vi.fn(async () => { - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) }) const secondSubTaskFunction = vi.fn(async () => { - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) }) const firstTask = { @@ -416,5 +416,5 @@ describe('Tasks', () => { }) async function taskHasRendered() { - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 10)) } diff --git a/packages/cli-kit/src/private/node/ui/components/TextAnimation.test.tsx b/packages/cli-kit/src/private/node/ui/components/TextAnimation.test.tsx index 3593110ae71..35f8841bba6 100644 --- a/packages/cli-kit/src/private/node/ui/components/TextAnimation.test.tsx +++ b/packages/cli-kit/src/private/node/ui/components/TextAnimation.test.tsx @@ -69,7 +69,7 @@ describe('TextAnimation', () => { stdout.columns = 120 resizeHandler() - await new Promise((resolve) => setTimeout(resolve, 50)) + await new Promise((resolve) => setTimeout(resolve, 10)) renderInstance.unmount() }) diff --git a/packages/cli-kit/src/private/node/ui/components/TextInput.test.tsx b/packages/cli-kit/src/private/node/ui/components/TextInput.test.tsx index 2f7e8a48a6d..0beaf7668bb 100644 --- a/packages/cli-kit/src/private/node/ui/components/TextInput.test.tsx +++ b/packages/cli-kit/src/private/node/ui/components/TextInput.test.tsx @@ -53,7 +53,7 @@ describe('TextInput', () => { await sendInputAndWaitForChange(renderInstance, ARROW_LEFT) expect(renderInstance.lastFrame()).toMatchInlineSnapshot('"Hello"') // cursor can't go before the first character - await sendInputAndWait(renderInstance, 100, ARROW_LEFT) + await sendInputAndWait(renderInstance, 10, ARROW_LEFT) expect(renderInstance.lastFrame()).toMatchInlineSnapshot('"Hello"') await sendInputAndWaitForChange(renderInstance, ARROW_RIGHT) @@ -67,7 +67,7 @@ describe('TextInput', () => { await sendInputAndWaitForChange(renderInstance, ARROW_RIGHT) expect(renderInstance.lastFrame()).toMatchInlineSnapshot('"Hello█"') // cursor can't go after the last character - await sendInputAndWait(renderInstance, 100, ARROW_RIGHT) + await sendInputAndWait(renderInstance, 10, ARROW_RIGHT) expect(renderInstance.lastFrame()).toMatchInlineSnapshot('"Hello█"') }) @@ -106,7 +106,7 @@ describe('TextInput', () => { await sendInputAndWaitForChange(renderInstance, DELETE) expect(renderInstance.lastFrame()).toMatchInlineSnapshot('"█"') // cannot delete after the value has been cleared - await sendInputAndWait(renderInstance, 100, DELETE) + await sendInputAndWait(renderInstance, 10, DELETE) expect(renderInstance.lastFrame()).toMatchInlineSnapshot('"█"') }) @@ -170,7 +170,7 @@ describe('TextInput', () => { expect(renderInstance.lastFrame()).toMatchInlineSnapshot('"Tet"') await sendInputAndWaitForChange(renderInstance, ARROW_LEFT) await sendInputAndWaitForChange(renderInstance, ARROW_LEFT) - await sendInputAndWait(renderInstance, 100, DELETE) + await sendInputAndWait(renderInstance, 10, DELETE) expect(renderInstance.lastFrame()).toMatchInlineSnapshot('"Tet"') }) diff --git a/packages/cli-kit/src/public/node/api/rest-api-throttler.test.ts b/packages/cli-kit/src/public/node/api/rest-api-throttler.test.ts new file mode 100644 index 00000000000..d99ac9f02db --- /dev/null +++ b/packages/cli-kit/src/public/node/api/rest-api-throttler.test.ts @@ -0,0 +1,73 @@ +import {extractRetryDelayMsFromResponse, extractApiCallLimitFromResponse} from './rest-api-throttler.js' +import {describe, test, expect, beforeEach} from 'vitest' +import type {RestResponse} from './admin.js' + +let response: RestResponse + +beforeEach(() => { + response = { + json: {}, + status: 200, + headers: {}, + } +}) + +describe('retryAfter', () => { + test('when the "retry-after" header value is valid', async () => { + response.headers = { + 'retry-after': ['2.0'], + } + const retryAfterDelay = extractRetryDelayMsFromResponse(response) + expect(retryAfterDelay).toBe(2) + }) + + test('when the "retry-after" header value is not present', async () => { + response.headers = { + 'retry-after': [], + } + const retryAfterDelay = extractRetryDelayMsFromResponse(response) + expect(retryAfterDelay).toBe(0) + }) + + test('when the "retry-after" header value is valid', async () => { + response.headers = { + 'retry-after': ['invalid'], + } + const retryAfterDelay = extractRetryDelayMsFromResponse(response) + expect(retryAfterDelay).toBe(0) + }) + + test('when the "retry-after" header is not present', async () => { + response.headers = {} + const retryAfterDelay = extractRetryDelayMsFromResponse(response) + expect(retryAfterDelay).toBe(0) + }) +}) + +describe('apiCallLimit', () => { + test('when the "x-shopify-shop-api-call-limit" header is valid', async () => { + response.headers = { + 'x-shopify-shop-api-call-limit': ['10/40'], + } + const callLimit = extractApiCallLimitFromResponse(response) + const [used, limit] = callLimit! + expect(used).toBe(10) + expect(limit).toBe(40) + }) + + test('when the "x-shopify-shop-api-call-limit" header is invalid', async () => { + response.headers = { + 'x-shopify-shop-api-call-limit': ['foo/bar'], + } + const callLimit = extractApiCallLimitFromResponse(response) + expect(callLimit).toBeUndefined() + }) + + test('when the "x-shopify-shop-api-call-limit" header is not formatted as expected', async () => { + response.headers = { + 'x-shopify-shop-api-call-limit': ['/10'], + } + const callLimit = extractApiCallLimitFromResponse(response) + expect(callLimit).toBeUndefined() + }) +}) diff --git a/packages/cli-kit/src/public/node/api/rest-api-throttler.ts b/packages/cli-kit/src/public/node/api/rest-api-throttler.ts index 2029cf952c7..15f127fa815 100644 --- a/packages/cli-kit/src/public/node/api/rest-api-throttler.ts +++ b/packages/cli-kit/src/public/node/api/rest-api-throttler.ts @@ -144,7 +144,13 @@ interface ThrottlingState { const _throttlingState: Record = {} -function extractRetryDelayMsFromResponse(response: RestResponse): number { +/** + * Extracts the retry delay in milliseconds from the response's `retry-after` header. + * + * @param response - The response object. + * @returns The retry delay in milliseconds, or 0 if not present/invalid. + */ +export function extractRetryDelayMsFromResponse(response: RestResponse): number { const retryAfterStr = header(response, 'retry-after') const retryAfter = tryParseInt(retryAfterStr) @@ -174,7 +180,13 @@ export async function delayAwareRetry( }) } -function extractApiCallLimitFromResponse(response: RestResponse): [number, number] | undefined { +/** + * Extracts the API call limit (used/total) from the response's `x-shopify-shop-api-call-limit` header. + * + * @param response - The response object. + * @returns A tuple of [used, limit], or undefined if the header is missing/invalid. + */ +export function extractApiCallLimitFromResponse(response: RestResponse): [number, number] | undefined { const apiCallLimit = header(response, 'x-shopify-shop-api-call-limit') const [used, limit] = apiCallLimit @@ -199,112 +211,3 @@ function header(response: RestResponse, name: string): string { return '' } - -if (import.meta.vitest) { - const {describe, test, expect, beforeEach} = import.meta.vitest - let response: RestResponse - - beforeEach(() => { - response = { - json: {}, - status: 200, - headers: {}, - } - }) - - describe('retryAfter', () => { - test('when the "retry-after" header value is valid', async () => { - // Given - response.headers = { - 'retry-after': ['2.0'], - } - - // When - const retryAfterDelay = extractRetryDelayMsFromResponse(response) - - // Then - expect(retryAfterDelay).toBe(2) - }) - - test('when the "retry-after" header value is not present', async () => { - // Given - response.headers = { - 'retry-after': [], - } - - // When - const retryAfterDelay = extractRetryDelayMsFromResponse(response) - - // Then - expect(retryAfterDelay).toBe(0) - }) - - test('when the "retry-after" header value is valid', async () => { - // Given - response.headers = { - 'retry-after': ['invalid'], - } - - // When - const retryAfterDelay = extractRetryDelayMsFromResponse(response) - - // Then - expect(retryAfterDelay).toBe(0) - }) - - test('when the "retry-after" header is not present', async () => { - // Given - response.headers = {} - - // When - const retryAfterDelay = extractRetryDelayMsFromResponse(response) - - // Then - expect(retryAfterDelay).toBe(0) - }) - }) - - describe('apiCallLimit', () => { - test('when the "x-shopify-shop-api-call-limit" header is valid', async () => { - // Given - response.headers = { - 'x-shopify-shop-api-call-limit': ['10/40'], - } - - // When - const callLimit = extractApiCallLimitFromResponse(response) - - const [used, limit] = callLimit! - - // Then - expect(used).toBe(10) - expect(limit).toBe(40) - }) - - test('when the "x-shopify-shop-api-call-limit" header is invalid', async () => { - // Given - response.headers = { - 'x-shopify-shop-api-call-limit': ['foo/bar'], - } - - // When - const callLimit = extractApiCallLimitFromResponse(response) - - // Then - expect(callLimit).toBeUndefined() - }) - - test('when the "x-shopify-shop-api-call-limit" header is not formatted as expected', async () => { - // Given - response.headers = { - 'x-shopify-shop-api-call-limit': ['/10'], - } - - // When - const callLimit = extractApiCallLimitFromResponse(response) - - // Then - expect(callLimit).toBeUndefined() - }) - }) -} diff --git a/packages/cli-kit/src/public/node/tcp.test.ts b/packages/cli-kit/src/public/node/tcp.test.ts index 8adc7202b7e..530b595fa9f 100644 --- a/packages/cli-kit/src/public/node/tcp.test.ts +++ b/packages/cli-kit/src/public/node/tcp.test.ts @@ -31,7 +31,9 @@ describe('getAvailableTCPPort', () => { } // When/Then - await expect(() => getAvailableTCPPort()).rejects.toThrowError(new AbortError(errorMessage)) + await expect(() => getAvailableTCPPort(undefined, {waitTimeInSeconds: 0})).rejects.toThrowError( + new AbortError(errorMessage), + ) }) test('returns the provided port when it is available', async () => { diff --git a/packages/theme/src/cli/services/list.test.ts b/packages/theme/src/cli/services/list.test.ts index 3d2f2ffe38a..cb8787d9452 100644 --- a/packages/theme/src/cli/services/list.test.ts +++ b/packages/theme/src/cli/services/list.test.ts @@ -90,18 +90,18 @@ describe('list', () => { await list({json: true}, session) expect(mockOutput.info()).toMatchInlineSnapshot(` - "[ - { - \\"id\\": 1, - \\"name\\": \\"Theme 1\\", - \\"role\\": \\"live\\" - }, - { - \\"id\\": 2, - \\"name\\": \\"Theme 2\\", - \\"role\\": \\"\\" - } - ]" - `) + "[ + { + "id": 1, + "name": "Theme 1", + "role": "live" + }, + { + "id": 2, + "name": "Theme 2", + "role": "" + } + ]" + `) }) }) diff --git a/packages/theme/src/cli/utilities/theme-environment/proxy.test.ts b/packages/theme/src/cli/utilities/theme-environment/proxy.test.ts index 38434b60856..f55593c3112 100644 --- a/packages/theme/src/cli/utilities/theme-environment/proxy.test.ts +++ b/packages/theme/src/cli/utilities/theme-environment/proxy.test.ts @@ -47,8 +47,8 @@ describe('dev proxy', () => { expect(injectCdnProxy(content, ctx)).toMatchInlineSnapshot(` " - - + + " @@ -68,9 +68,9 @@ describe('dev proxy', () => { expect(injectCdnProxy(content, ctx)).toMatchInlineSnapshot(` " - - - + + + " @@ -90,9 +90,9 @@ describe('dev proxy', () => { expect(injectCdnProxy(content, ctx)).toMatchInlineSnapshot(` " - - - + + + " @@ -112,7 +112,7 @@ describe('dev proxy', () => { " console.log('/cdn/path/to/assets/file1'); // Comment: /cdn/path/to/assets/file1 something - const url = \\"/cdn/path/to/assets/file1#zzz\\"; + const url = "/cdn/path/to/assets/file1#zzz"; fetch(\`/cdn/path/to/assets/file1?q=123\`); " `, @@ -125,7 +125,7 @@ describe('dev proxy', () => { `; as="style"; rel="preload"` expect(injectCdnProxy(linkHeader, ctx)).toMatchInlineSnapshot( - `"; rel=\\"preconnect\\", ; rel=\\"preconnect\\"; crossorigin,; as=\\"style\\"; rel=\\"preload\\""`, + `"; rel="preconnect", ; rel="preconnect"; crossorigin,; as="style"; rel="preload""`, ) }) @@ -141,7 +141,7 @@ describe('dev proxy', () => { " -
+
" `) @@ -166,8 +166,8 @@ describe('dev proxy', () => { expect(injectCdnProxy(content, ctx)).toMatchInlineSnapshot(` " -
- +
+ " `) @@ -225,17 +225,17 @@ describe('dev proxy', () => { await expect(patchedResponse.text()).resolves.toMatchInlineSnapshot(` " - - + + -
+
" `) expect(patchedResponse.headers.get('link')).toMatchInlineSnapshot( - `"; rel=\\"preconnect\\", ; rel=\\"preconnect\\"; crossorigin,; as=\\"style\\"; rel=\\"preload\\""`, + `"; rel="preconnect", ; rel="preconnect"; crossorigin,; as="style"; rel="preload""`, ) expect(patchedResponse.headers.getSetCookie()).toMatchInlineSnapshot( diff --git a/packages/theme/src/cli/utilities/theme-environment/theme-environment.test.ts b/packages/theme/src/cli/utilities/theme-environment/theme-environment.test.ts index cb6e7ef4cf5..e6bc84ba15c 100644 --- a/packages/theme/src/cli/utilities/theme-environment/theme-environment.test.ts +++ b/packages/theme/src/cli/utilities/theme-environment/theme-environment.test.ts @@ -267,7 +267,7 @@ describe('setupDevServer', () => { expect(res.getHeader('content-type')).toEqual('text/css') // The URL is proxied: expect(body.toString()).toMatchInlineSnapshot( - `".some-class { background: url(\\"/cdn/path/to/assets/file2.css\\") }"`, + `".some-class { background: url("/cdn/path/to/assets/file2.css") }"`, ) }) @@ -435,9 +435,9 @@ describe('setupDevServer', () => { var __blocks__ = {}; (function () { - var element = document.getElementById(\\"blocks-script\\"); - var attribute = element ? element.getAttribute(\\"data-blocks\\") : \\"\\"; - var blocks = attribute.split(\\",\\").filter(Boolean); + var element = document.getElementById("blocks-script"); + var attribute = element ? element.getAttribute("data-blocks") : ""; + var blocks = attribute.split(",").filter(Boolean); for (var i = 0; i < blocks.length; i++) { __blocks__[blocks[i]] = true; @@ -445,24 +445,24 @@ describe('setupDevServer', () => { })(); (function () { - if (!__blocks__[\\"another-block\\"] && !Shopify.designMode) return; + if (!__blocks__["another-block"] && !Shopify.designMode) return; try { /* blocks/another-block.liquid */ console.log('This is another block script'); - ${keepIndent} + } catch (e) { console.error(e); } })(); (function () { - if (!__blocks__[\\"test-block\\"] && !Shopify.designMode) return; + if (!__blocks__["test-block"] && !Shopify.designMode) return; try { /* blocks/test-block.liquid */ console.log('This is block script'); - ${keepIndent} + } catch (e) { console.error(e); } @@ -520,9 +520,9 @@ describe('setupDevServer', () => { var __snippets__ = {}; (function () { - var element = document.getElementById(\\"snippets-script\\"); - var attribute = element ? element.getAttribute(\\"data-snippets\\") : \\"\\"; - var snippets = attribute.split(\\",\\").filter(Boolean); + var element = document.getElementById("snippets-script"); + var attribute = element ? element.getAttribute("data-snippets") : ""; + var snippets = attribute.split(",").filter(Boolean); for (var i = 0; i < snippets.length; i++) { __snippets__[snippets[i]] = true; @@ -530,24 +530,24 @@ describe('setupDevServer', () => { })(); (function () { - if (!__snippets__[\\"another-snippet\\"] && !Shopify.designMode) return; + if (!__snippets__["another-snippet"] && !Shopify.designMode) return; try { /* snippets/another-snippet.liquid */ console.log('This is another snippet script'); - ${keepIndent} + } catch (e) { console.error(e); } })(); (function () { - if (!__snippets__[\\"test-snippet\\"] && !Shopify.designMode) return; + if (!__snippets__["test-snippet"] && !Shopify.designMode) return; try { /* snippets/test-snippet.liquid */ console.log('This is snippet script'); - ${keepIndent} + } catch (e) { console.error(e); } @@ -604,9 +604,9 @@ describe('setupDevServer', () => { var __sections__ = {}; (function () { - var element = document.getElementById(\\"sections-script\\"); - var attribute = element ? element.getAttribute(\\"data-sections\\") : \\"\\"; - var sections = attribute.split(\\",\\").filter(Boolean); + var element = document.getElementById("sections-script"); + var attribute = element ? element.getAttribute("data-sections") : ""; + var sections = attribute.split(",").filter(Boolean); for (var i = 0; i < sections.length; i++) { __sections__[sections[i]] = true; @@ -614,24 +614,24 @@ describe('setupDevServer', () => { })(); (function () { - if (!__sections__[\\"another-section\\"] && !Shopify.designMode) return; + if (!__sections__["another-section"] && !Shopify.designMode) return; try { /* sections/another-section.liquid */ console.log('This is another section script'); - ${keepIndent} + } catch (e) { console.error(e); } })(); (function () { - if (!__sections__[\\"test-section\\"] && !Shopify.designMode) return; + if (!__sections__["test-section"] && !Shopify.designMode) return; try { /* sections/test-section.liquid */ console.log('This is section script'); - ${keepIndent} + } catch (e) { console.error(e); } diff --git a/vite.config.ts b/vite.config.ts index 19a85c7025f..df423fbcdbf 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,3 +1,19 @@ -import config from './configurations/vite.config' +import {defineConfig} from 'vitest/config' -export default config(__dirname) +export default defineConfig({ + test: { + coverage: { + provider: 'istanbul', + }, + projects: [ + 'packages/app/vite.config.ts', + 'packages/cli/vite.config.ts', + 'packages/cli-kit/vite.config.ts', + 'packages/plugin-cloudflare/vite.config.ts', + 'packages/plugin-did-you-mean/vite.config.ts', + 'packages/theme/vite.config.ts', + 'packages/ui-extensions-dev-console/vite.config.mts', + 'packages/ui-extensions-server-kit/vite.config.mts', + ], + }, +}) diff --git a/vitest.workspace.json b/vitest.workspace.json deleted file mode 100644 index b6523d9888e..00000000000 --- a/vitest.workspace.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - "packages/app", - "packages/cli", - "packages/cli-kit", - "packages/plugin-cloudflare", - "packages/plugin-did-you-mean", - "packages/theme", - "packages/ui-extensions-dev-console", - "packages/ui-extensions-server-kit" -]