diff --git a/packages/theme/src/cli/services/push.test.ts b/packages/theme/src/cli/services/push.test.ts index 7344b348c5..78513c000f 100644 --- a/packages/theme/src/cli/services/push.test.ts +++ b/packages/theme/src/cli/services/push.test.ts @@ -1,5 +1,6 @@ -import {createOrSelectTheme, push} from './push.js' +import {createOrSelectTheme, push, ThemeSelectionOptions} from './push.js' import {PullFlags} from './pull.js' +import {setDevelopmentTheme} from './local-storage.js' import {uploadTheme} from '../utilities/theme-uploader.js' import {ensureThemeStore} from '../utilities/theme-store.js' import {findOrSelectTheme} from '../utilities/theme-selector.js' @@ -7,16 +8,22 @@ import {buildTheme} from '@shopify/cli-kit/node/themes/factories' import {test, describe, vi, expect, beforeEach} from 'vitest' import {createTheme, fetchTheme, publishTheme} from '@shopify/cli-kit/node/themes/api' import {ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session' -import {promptThemeName, UNPUBLISHED_THEME_ROLE} from '@shopify/cli-kit/node/themes/utils' +import { + DEVELOPMENT_THEME_ROLE, + LIVE_THEME_ROLE, + promptThemeName, + UNPUBLISHED_THEME_ROLE, +} from '@shopify/cli-kit/node/themes/utils' +import {renderConfirmationPrompt} from '@shopify/cli-kit/node/ui' -vi.mock('../utilities/theme-fs.js') vi.mock('../utilities/theme-uploader.js') vi.mock('../utilities/theme-store.js') vi.mock('../utilities/theme-selector.js') +vi.mock('./local-storage.js') vi.mock('@shopify/cli-kit/node/themes/utils') vi.mock('@shopify/cli-kit/node/session') -vi.mock('@shopify/cli-kit/node/path') vi.mock('@shopify/cli-kit/node/themes/api') +vi.mock('@shopify/cli-kit/node/ui') const path = '/my-theme' const defaultFlags: PullFlags = { @@ -54,20 +61,123 @@ describe('push', () => { }) }) -describe('createOrSelectTheme', () => { +describe('createOrSelectTheme', async () => { test('creates unpublished theme when unpublished flag is provided', async () => { // Given vi.mocked(createTheme).mockResolvedValue(buildTheme({id: 2, name: 'Theme', role: UNPUBLISHED_THEME_ROLE})) vi.mocked(fetchTheme).mockResolvedValue(undefined) - vi.mocked(promptThemeName).mockResolvedValue('Theme') - const flags = {unpublished: true} + const flags: ThemeSelectionOptions = {unpublished: true} // When const theme = await createOrSelectTheme(adminSession, flags) // Then - expect(createTheme).toHaveBeenCalledWith({name: 'Theme', role: UNPUBLISHED_THEME_ROLE}, adminSession) expect(theme).toMatchObject({role: UNPUBLISHED_THEME_ROLE}) + expect(setDevelopmentTheme).not.toHaveBeenCalled() + }) + + test('creates development theme when development flag is provided', async () => { + // Given + vi.mocked(createTheme).mockResolvedValue(buildTheme({id: 1, name: 'Theme', role: DEVELOPMENT_THEME_ROLE})) + vi.mocked(fetchTheme).mockResolvedValue(undefined) + const flags: ThemeSelectionOptions = {development: true} + + // When + const theme = await createOrSelectTheme(adminSession, flags) + + // Then + expect(theme).toMatchObject({role: DEVELOPMENT_THEME_ROLE}) + expect(setDevelopmentTheme).toHaveBeenCalled() + }) + + test('creates development theme when development and unpublished flags are provided', async () => { + // Given + vi.mocked(createTheme).mockResolvedValue(buildTheme({id: 1, name: 'Theme', role: DEVELOPMENT_THEME_ROLE})) + vi.mocked(fetchTheme).mockResolvedValue(undefined) + const flags: ThemeSelectionOptions = {development: true, unpublished: true} + + // When + const theme = await createOrSelectTheme(adminSession, flags) + + // Then + expect(theme).toMatchObject({role: DEVELOPMENT_THEME_ROLE}) + }) + + test('returns live theme when live flag is provided', async () => { + // Given + vi.mocked(findOrSelectTheme).mockResolvedValue(buildTheme({id: 3, name: 'Live Theme', role: LIVE_THEME_ROLE})!) + const flags: ThemeSelectionOptions = {live: true, 'allow-live': true} + + // When + const theme = await createOrSelectTheme(adminSession, flags) + + // Then + expect(theme).toMatchObject({role: LIVE_THEME_ROLE}) + }) + + test("renders confirmation prompt if 'allow-live' flag is not provided and selected theme role is live", async () => { + // Given + vi.mocked(findOrSelectTheme).mockResolvedValue(buildTheme({id: 3, name: 'Live Theme', role: LIVE_THEME_ROLE})!) + vi.mocked(renderConfirmationPrompt).mockResolvedValue(true) + const flags: ThemeSelectionOptions = {live: true} + + // When + const theme = await createOrSelectTheme(adminSession, flags) + + // Then + expect(theme?.role).toBe(LIVE_THEME_ROLE) + expect(renderConfirmationPrompt).toHaveBeenCalled() + }) + + test("renders confirmation prompt if 'allow-live' flag is not provided and live theme is specified via theme flag", async () => { + // Given + vi.mocked(findOrSelectTheme).mockResolvedValue(buildTheme({id: 3, name: 'Live Theme', role: LIVE_THEME_ROLE})!) + vi.mocked(renderConfirmationPrompt).mockResolvedValue(true) + const flags: ThemeSelectionOptions = {theme: '3'} + + // When + const theme = await createOrSelectTheme(adminSession, flags) + + // Then + expect(theme?.role).toBe(LIVE_THEME_ROLE) + expect(renderConfirmationPrompt).toHaveBeenCalled() + }) + + test('returns undefined if live theme confirmation prompt is not confirmed', async () => { + // Given + vi.mocked(findOrSelectTheme).mockResolvedValue(buildTheme({id: 3, name: 'Live Theme', role: LIVE_THEME_ROLE})!) + vi.mocked(renderConfirmationPrompt).mockResolvedValue(false) + const flags: ThemeSelectionOptions = {live: true} + + // When + const theme = await createOrSelectTheme(adminSession, flags) + + // Then + expect(theme).toBeUndefined() + }) + + test('returns undefined if confirmation prompt is rejected', async () => { + // Given + vi.mocked(findOrSelectTheme).mockResolvedValue(buildTheme({id: 3, name: 'Live Theme', role: LIVE_THEME_ROLE})!) + vi.mocked(renderConfirmationPrompt).mockResolvedValue(false) + const flags = {live: true} + + // When + const theme = await createOrSelectTheme(adminSession, flags) + + // Then + expect(theme).toBeUndefined() + }) + + test('renders text prompt if unpublished flag is provided and theme flag is not provided', async () => { + // Given + const flags = {unpublished: true} + + // When + await createOrSelectTheme(adminSession, flags) + + // Then + expect(promptThemeName).toHaveBeenCalledWith('Name of the new theme') }) }) diff --git a/packages/theme/src/cli/services/push.ts b/packages/theme/src/cli/services/push.ts index feb02783ca..3e95bbf657 100644 --- a/packages/theme/src/cli/services/push.ts +++ b/packages/theme/src/cli/services/push.ts @@ -20,7 +20,7 @@ import {themeEditorUrl, themePreviewUrl} from '@shopify/cli-kit/node/themes/urls import {cwd, resolvePath} from '@shopify/cli-kit/node/path' import {LIVE_THEME_ROLE, promptThemeName, UNPUBLISHED_THEME_ROLE} from '@shopify/cli-kit/node/themes/utils' -interface ThemeSelectionOptions { +export interface ThemeSelectionOptions { live?: boolean development?: boolean unpublished?: boolean @@ -107,25 +107,9 @@ export interface PushFlags { * Initiates the push process based on provided flags. * * @param flags - The flags for the push operation. - * @param flags.path - The path to your theme directory. - * @param flags.password - Password generated from the Theme Access app. - * @param flags.store - Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com). - * @param flags.environment - The environment to apply to the current command. - * @param flags.theme - Theme ID or name of the remote theme. - * @param flags.development - Push theme files from your remote development theme. - * @param flags.live - Push theme files from your remote live theme. - * @param flags.unpublished - Create a new unpublished theme and push to it. - * @param flags.nodelete - Runs the push command without deleting local files. - * @param flags.only - Download only the specified files (Multiple flags allowed). - * @param flags.ignore - Skip downloading the specified files (Multiple flags allowed). - * @param flags.json - Output JSON instead of a UI. - * @param flags.allowLive - Allow push to a live theme. - * @param flags.publish - Publish as the live theme after uploading. - * @param flags.force - Proceed without confirmation, if current directory does not seem to be theme directory. - * @param flags.noColor - Disable color output. - * @param flags.verbose - Increase the verbosity of the output. + * @returns {Promise} Resolves when the push operation is complete. */ -export async function push(flags: PushFlags) { +export async function push(flags: PushFlags): Promise { const {path} = flags const force = flags.force ?? false