|
| 1 | +import { getMockOclifConfig } from '../../../../__tests__/commands/utils'; |
| 2 | +import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient'; |
| 3 | +import { AppPlatform } from '../../../../graphql/generated'; |
| 4 | +import { |
| 5 | + EmbeddedUpdateFragment, |
| 6 | + EmbeddedUpdateQuery, |
| 7 | +} from '../../../../graphql/queries/EmbeddedUpdateQuery'; |
| 8 | +import Log from '../../../../log'; |
| 9 | +import * as json from '../../../../utils/json'; |
| 10 | +import UpdateEmbeddedList from '../list'; |
| 11 | + |
| 12 | +jest.mock('../../../../graphql/queries/EmbeddedUpdateQuery', () => ({ |
| 13 | + EmbeddedUpdateQuery: { viewPaginatedAsync: jest.fn() }, |
| 14 | +})); |
| 15 | +jest.mock('../../../../log'); |
| 16 | +jest.mock('../../../../utils/json'); |
| 17 | + |
| 18 | +const mockPaginated = jest.mocked(EmbeddedUpdateQuery.viewPaginatedAsync); |
| 19 | +const mockLogLog = jest.mocked(Log.log); |
| 20 | +const mockEnableJsonOutput = jest.mocked(json.enableJsonOutput); |
| 21 | +const mockPrintJson = jest.mocked(json.printJsonOnlyOutput); |
| 22 | + |
| 23 | +const MOCK_CONTEXT = { |
| 24 | + projectId: 'project-123', |
| 25 | + loggedIn: { graphqlClient: {} as ExpoGraphqlClient }, |
| 26 | +}; |
| 27 | + |
| 28 | +const ROW_A: EmbeddedUpdateFragment = { |
| 29 | + id: 'aaaaaaaa-1111-4000-8000-000000000001', |
| 30 | + platform: AppPlatform.Ios, |
| 31 | + runtimeVersion: '1.0.0', |
| 32 | + channel: 'production', |
| 33 | + createdAt: '2026-05-29T00:00:00Z', |
| 34 | +}; |
| 35 | +const ROW_B: EmbeddedUpdateFragment = { |
| 36 | + id: 'bbbbbbbb-2222-4000-8000-000000000002', |
| 37 | + platform: AppPlatform.Android, |
| 38 | + runtimeVersion: '1.0.1', |
| 39 | + channel: 'preview', |
| 40 | + createdAt: '2026-05-30T00:00:00Z', |
| 41 | +}; |
| 42 | + |
| 43 | +function emptyConnection(): { |
| 44 | + edges: { cursor: string; node: EmbeddedUpdateFragment }[]; |
| 45 | + pageInfo: { |
| 46 | + hasNextPage: boolean; |
| 47 | + hasPreviousPage: boolean; |
| 48 | + startCursor: string | null; |
| 49 | + endCursor: string | null; |
| 50 | + }; |
| 51 | +} { |
| 52 | + return { |
| 53 | + edges: [], |
| 54 | + pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: null, endCursor: null }, |
| 55 | + }; |
| 56 | +} |
| 57 | + |
| 58 | +describe(UpdateEmbeddedList, () => { |
| 59 | + const mockConfig = getMockOclifConfig(); |
| 60 | + |
| 61 | + beforeEach(() => { |
| 62 | + jest.clearAllMocks(); |
| 63 | + }); |
| 64 | + |
| 65 | + function createCommand(argv: string[]): UpdateEmbeddedList { |
| 66 | + const command = new UpdateEmbeddedList(argv, mockConfig); |
| 67 | + // @ts-expect-error getContextAsync is protected |
| 68 | + jest.spyOn(command, 'getContextAsync').mockResolvedValue(MOCK_CONTEXT); |
| 69 | + return command; |
| 70 | + } |
| 71 | + |
| 72 | + it('prints each row when results exist', async () => { |
| 73 | + mockPaginated.mockResolvedValue({ |
| 74 | + edges: [ |
| 75 | + { cursor: 'c1', node: ROW_A }, |
| 76 | + { cursor: 'c2', node: ROW_B }, |
| 77 | + ], |
| 78 | + pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c2' }, |
| 79 | + }); |
| 80 | + await createCommand([]).run(); |
| 81 | + |
| 82 | + expect(mockPaginated).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, { |
| 83 | + appId: MOCK_CONTEXT.projectId, |
| 84 | + filter: undefined, |
| 85 | + first: 25, |
| 86 | + after: undefined, |
| 87 | + }); |
| 88 | + // Two rows printed (formatted multi-line, one Log.log per row) |
| 89 | + expect(mockLogLog.mock.calls.some(c => String(c[0]).includes(ROW_A.id))).toBe(true); |
| 90 | + expect(mockLogLog.mock.calls.some(c => String(c[0]).includes(ROW_B.id))).toBe(true); |
| 91 | + }); |
| 92 | + |
| 93 | + it('prints empty message when no results', async () => { |
| 94 | + mockPaginated.mockResolvedValue(emptyConnection()); |
| 95 | + await createCommand([]).run(); |
| 96 | + expect(mockLogLog).toHaveBeenCalledWith('No embedded updates found.'); |
| 97 | + }); |
| 98 | + |
| 99 | + it('--json prints connection payload and skips formatted output', async () => { |
| 100 | + mockPaginated.mockResolvedValue({ |
| 101 | + edges: [{ cursor: 'c1', node: ROW_A }], |
| 102 | + pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c1' }, |
| 103 | + }); |
| 104 | + await createCommand(['--json', '--non-interactive']).run(); |
| 105 | + |
| 106 | + expect(mockEnableJsonOutput).toHaveBeenCalled(); |
| 107 | + expect(mockPrintJson).toHaveBeenCalledWith({ |
| 108 | + embeddedUpdates: [ROW_A], |
| 109 | + pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c1' }, |
| 110 | + }); |
| 111 | + // No formatted Log.log of the row |
| 112 | + expect(mockLogLog.mock.calls.every(c => !String(c[0]).includes(ROW_A.id))).toBe(true); |
| 113 | + }); |
| 114 | + |
| 115 | + it('passes filter when flags supplied', async () => { |
| 116 | + mockPaginated.mockResolvedValue(emptyConnection()); |
| 117 | + await createCommand([ |
| 118 | + '--platform', |
| 119 | + 'ios', |
| 120 | + '--runtime-version', |
| 121 | + '1.2.0', |
| 122 | + '--channel', |
| 123 | + 'preview', |
| 124 | + ]).run(); |
| 125 | + |
| 126 | + expect(mockPaginated).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, { |
| 127 | + appId: MOCK_CONTEXT.projectId, |
| 128 | + filter: { platform: AppPlatform.Ios, runtimeVersion: '1.2.0', channel: 'preview' }, |
| 129 | + first: 25, |
| 130 | + after: undefined, |
| 131 | + }); |
| 132 | + }); |
| 133 | + |
| 134 | + it('shows next-page hint when hasNextPage', async () => { |
| 135 | + mockPaginated.mockResolvedValue({ |
| 136 | + edges: [{ cursor: 'c1', node: ROW_A }], |
| 137 | + pageInfo: { hasNextPage: true, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c1' }, |
| 138 | + }); |
| 139 | + await createCommand([]).run(); |
| 140 | + |
| 141 | + expect( |
| 142 | + mockLogLog.mock.calls.some(c => String(c[0]).includes('--after-cursor c1')) |
| 143 | + ).toBe(true); |
| 144 | + }); |
| 145 | +}); |
0 commit comments