|
| 1 | +import Share from './share.js' |
| 2 | +import {executeThemePush} from '../../services/push.js' |
| 3 | +import {themeShareJsonOutputSchema} from '../../services/share/types.js' |
| 4 | +import {captureStandardStreams} from '../../utilities/test-helpers/stdio.js' |
| 5 | +import {Config} from '@oclif/core' |
| 6 | +import {getRandomName} from '@shopify/cli-kit/common/string' |
| 7 | +import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output' |
| 8 | +import {describe, expect, test, vi} from 'vitest' |
| 9 | + |
| 10 | +vi.mock('../../services/push.js') |
| 11 | +vi.mock('@shopify/cli-kit/common/string', async (importOriginal) => ({ |
| 12 | + ...(await importOriginal<typeof import('@shopify/cli-kit/common/string')>()), |
| 13 | + getRandomName: vi.fn(), |
| 14 | +})) |
| 15 | + |
| 16 | +const session = {storeFqdn: 'test.myshopify.com', token: 'token'} |
| 17 | + |
| 18 | +function result() { |
| 19 | + return { |
| 20 | + theme: { |
| 21 | + id: 1, |
| 22 | + name: 'Creative Theme', |
| 23 | + role: 'unpublished', |
| 24 | + shop: session.storeFqdn, |
| 25 | + editor_url: 'editor', |
| 26 | + preview_url: 'preview', |
| 27 | + }, |
| 28 | + published: false, |
| 29 | + hasErrors: false, |
| 30 | + errors: {}, |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +describe('theme share', () => { |
| 35 | + test('exposes JSON and schema flags and documents its own result', () => { |
| 36 | + expect(Share.jsonOutputSchema).toBe(themeShareJsonOutputSchema) |
| 37 | + expect(Share.description).toContain('ThemeShareResult') |
| 38 | + expect(Share.flags.json).toBeDefined() |
| 39 | + expect(Share.baseFlags).toHaveProperty('json-schema') |
| 40 | + }) |
| 41 | + |
| 42 | + test('creates an unpublished theme with a random name and forwards the listing', async () => { |
| 43 | + vi.mocked(getRandomName).mockReturnValue('Creative Theme') |
| 44 | + vi.mocked(executeThemePush).mockResolvedValue(result()) |
| 45 | + const command = new Share([], new Config({root: '.'})) |
| 46 | + const streams = await captureStandardStreams(() => |
| 47 | + command.command({json: true, path: '/theme', listing: 'preset'} as never, session, false), |
| 48 | + ) |
| 49 | + expect(getRandomName).toHaveBeenCalledWith('creative') |
| 50 | + expect(executeThemePush).toHaveBeenCalledWith( |
| 51 | + expect.objectContaining({unpublished: true, theme: 'Creative Theme', path: '/theme', listing: 'preset'}), |
| 52 | + session, |
| 53 | + false, |
| 54 | + undefined, |
| 55 | + ) |
| 56 | + expect(streams.stdout).toBe(`${themeShareJsonOutputSchema.encode({theme: result().theme})}\n`) |
| 57 | + expect(streams.stderr).toBe('') |
| 58 | + }) |
| 59 | + |
| 60 | + test('keeps upload errors and environment in JSON', async () => { |
| 61 | + vi.mocked(executeThemePush).mockResolvedValue({ |
| 62 | + ...result(), |
| 63 | + environment: 'staging', |
| 64 | + hasErrors: true, |
| 65 | + errors: {'assets/theme.css': ['bad CSS']}, |
| 66 | + }) |
| 67 | + const command = new Share([], new Config({root: '.'})) |
| 68 | + const streams = await captureStandardStreams(() => |
| 69 | + command.command({json: true, environment: ['staging']} as never, session, false), |
| 70 | + ) |
| 71 | + expect(JSON.parse(streams.stdout)).toEqual({ |
| 72 | + environment: 'staging', |
| 73 | + theme: { |
| 74 | + ...result().theme, |
| 75 | + warning: "[staging] The theme 'Creative Theme' was pushed with errors", |
| 76 | + errors: {'assets/theme.css': ['bad CSS']}, |
| 77 | + }, |
| 78 | + }) |
| 79 | + }) |
| 80 | + |
| 81 | + test('preserves the push success banner in text mode', async () => { |
| 82 | + vi.mocked(executeThemePush).mockResolvedValue(result()) |
| 83 | + const output = mockAndCaptureOutput() |
| 84 | + output.clear() |
| 85 | + await new Share([], new Config({root: '.'})).command({} as never, session, false) |
| 86 | + expect(output.info()).toContain("The theme 'Creative Theme' (#1) was pushed successfully.") |
| 87 | + expect(output.info()).toContain('View your theme') |
| 88 | + expect(output.info()).toContain('Customize your theme at the theme editor') |
| 89 | + }) |
| 90 | + |
| 91 | + test('does not emit a result when cancelled', async () => { |
| 92 | + vi.mocked(executeThemePush).mockResolvedValue(undefined) |
| 93 | + const streams = await captureStandardStreams(() => |
| 94 | + new Share([], new Config({root: '.'})).command({json: true} as never, session, false), |
| 95 | + ) |
| 96 | + expect(streams.stdout).toBe('') |
| 97 | + }) |
| 98 | + |
| 99 | + test('propagates theme creation failures through the shared error path', async () => { |
| 100 | + const failure = new Error('theme creation failed') |
| 101 | + vi.mocked(executeThemePush).mockRejectedValue(failure) |
| 102 | + const streams = await captureStandardStreams(async () => { |
| 103 | + await expect(new Share([], new Config({root: '.'})).command({json: true} as never, session, false)).rejects.toBe( |
| 104 | + failure, |
| 105 | + ) |
| 106 | + }) |
| 107 | + expect(streams.stdout).toBe('') |
| 108 | + }) |
| 109 | + |
| 110 | + test.each([{id: '1'}, {role: null}, {errors: {file: 'invalid'}}])('rejects malformed theme data %j', (fields) => { |
| 111 | + expect(() => themeShareJsonOutputSchema.validate({theme: {...result().theme, ...fields}})).toThrow() |
| 112 | + }) |
| 113 | +}) |
0 commit comments