@shopify/cli-kit
Version:
A set of utilities, interfaces, and models that are common across all the platform features
197 lines • 9.77 kB
JavaScript
import { SingleTask } from './SingleTask.js';
import { render } from '../../testing/ui.js';
import { TokenizedString } from '../../../../public/node/output.js';
import React from 'react';
import { describe, expect, test } from 'vitest';
describe('SingleTask', () => {
test('unmounts when promise resolves successfully', async () => {
// Given
const title = new TokenizedString('Uploading files');
let resolvePromise;
const task = () => new Promise((resolve) => {
resolvePromise = resolve;
});
// When
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
// Wait for initial render
await new Promise((resolve) => setTimeout(resolve, 10));
// Resolve the promise
resolvePromise('success');
// Wait for component to update and unmount
await renderInstance.waitUntilExit();
// Then - component should unmount cleanly
expect(renderInstance.lastFrame()).toBeDefined();
});
test('unmounts when promise rejects', async () => {
// Given
const title = new TokenizedString('Failed task');
let rejectPromise;
const task = () => new Promise((resolve, reject) => {
rejectPromise = reject;
});
// When
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
// Wait for initial render
await new Promise((resolve) => setTimeout(resolve, 10));
// Reject the promise and expect waitUntilExit to reject
rejectPromise(new Error('Task failed'));
// The component should exit with the error
await expect(renderInstance.waitUntilExit()).rejects.toThrow('Task failed');
});
test('handles promise that resolves immediately', async () => {
// Given
const title = new TokenizedString('Instant task');
const task = () => Promise.resolve('immediate success');
// When
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
await renderInstance.waitUntilExit();
// Then - component should complete successfully
expect(renderInstance.lastFrame()).toBeDefined();
});
test('handles promise that rejects immediately', async () => {
// Given
const title = new TokenizedString('Instant failure');
const task = () => Promise.reject(new Error('Immediate error'));
// When
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
// Then - should exit with error
await expect(renderInstance.waitUntilExit()).rejects.toThrow('Immediate error');
});
test('handles different types of promise return values', async () => {
// Test with string
let stringResult;
const stringTask = () => Promise.resolve('task completed');
const stringRender = render(React.createElement(SingleTask, { title: new TokenizedString('String task'), task: stringTask, onComplete: (result) => (stringResult = result) }));
await stringRender.waitUntilExit();
expect(stringRender.lastFrame()).toBeDefined();
expect(stringResult).toBe('task completed');
// Test with object
let objectResult;
const objectTask = () => Promise.resolve({ id: 1, name: 'test' });
const objectRender = render(React.createElement(SingleTask, { title: new TokenizedString('Object task'), task: objectTask, onComplete: (result) => (objectResult = result) }));
await objectRender.waitUntilExit();
expect(objectRender.lastFrame()).toBeDefined();
// Test with number
let numberResult;
const numberTask = () => Promise.resolve(42);
const numberRender = render(React.createElement(SingleTask, { title: new TokenizedString('Number task'), task: numberTask, onComplete: (result) => (numberResult = result) }));
await numberRender.waitUntilExit();
expect(numberRender.lastFrame()).toBeDefined();
expect(numberResult).toBe(42);
// Test with boolean
let booleanResult;
const booleanTask = () => Promise.resolve(true);
const booleanRender = render(React.createElement(SingleTask, { title: new TokenizedString('Boolean task'), task: booleanTask, onComplete: (result) => (booleanResult = result) }));
await booleanRender.waitUntilExit();
expect(booleanRender.lastFrame()).toBeDefined();
expect(booleanResult).toBe(true);
});
test('handles promise with delayed resolution', async () => {
// Given
const title = new TokenizedString('Delayed task');
const task = () => new Promise((resolve) => {
setTimeout(() => resolve('completed'), 100);
});
// When
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
// Wait for completion
await renderInstance.waitUntilExit();
// Then
expect(renderInstance.lastFrame()).toBeDefined();
});
test('handles promise with delayed rejection', async () => {
// Given
const title = new TokenizedString('Delayed failure');
const task = () => new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('delayed error')), 100);
});
// When
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
// Wait for completion - should throw error
await expect(renderInstance.waitUntilExit()).rejects.toThrow('delayed error');
});
test('preserves error types and messages', async () => {
// Test with custom error
class CustomError extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = 'CustomError';
}
}
const customError = new CustomError('Custom error message', 'CUSTOM_CODE');
const task = () => Promise.reject(customError);
// When
const renderInstance = render(React.createElement(SingleTask, { title: new TokenizedString('Custom error task'), task: task }));
// Then - should preserve the exact error
await expect(renderInstance.waitUntilExit()).rejects.toThrow('Custom error message');
});
test('handles concurrent promise operations', async () => {
// Given - Multiple SingleTask components with different promises
const fastPromise = () => new Promise((resolve) => setTimeout(() => resolve('fast'), 50));
const slowPromise = () => new Promise((resolve) => setTimeout(() => resolve('slow'), 150));
// When
const fastRender = render(React.createElement(SingleTask, { title: new TokenizedString('Fast task'), task: fastPromise }));
const slowRender = render(React.createElement(SingleTask, { title: new TokenizedString('Slow task'), task: slowPromise }));
// Then - Both should complete successfully
await fastRender.waitUntilExit();
await slowRender.waitUntilExit();
expect(fastRender.lastFrame()).toBeDefined();
expect(slowRender.lastFrame()).toBeDefined();
});
test('passes noColor prop to LoadingBar component', async () => {
// Given
const title = new TokenizedString('No color task');
const task = () => Promise.resolve();
// When - Test that noColor prop doesn't break the component
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task, noColor: true }));
await renderInstance.waitUntilExit();
// Then - Component should complete successfully with noColor prop
expect(renderInstance.lastFrame()).toBeDefined();
});
test('updates status message during task execution', async () => {
// Given
const initialTitle = new TokenizedString('Starting task');
let step1Resolve;
let step2Resolve;
let step3Resolve;
const step1Promise = new Promise((resolve) => {
step1Resolve = resolve;
});
const step2Promise = new Promise((resolve) => {
step2Resolve = resolve;
});
const step3Promise = new Promise((resolve) => {
step3Resolve = resolve;
});
const task = async (updateStatus) => {
updateStatus(new TokenizedString('Running (1 complete)...'));
await step1Promise;
updateStatus(new TokenizedString('Running (2 complete)...'));
await step2Promise;
updateStatus(new TokenizedString('Running (3 complete)...'));
await step3Promise;
return 'completed';
};
// When
const renderInstance = render(React.createElement(SingleTask, { title: initialTitle, task: task }));
// Wait for component to render with first status
await new Promise((resolve) => setTimeout(resolve, 10));
const frame1 = renderInstance.lastFrame();
expect(frame1).toContain('1 complete');
// Progress to step 2
step1Resolve();
await new Promise((resolve) => setTimeout(resolve, 10));
const frame2 = renderInstance.lastFrame();
expect(frame2).toContain('2 complete');
// Progress to step 3
step2Resolve();
await new Promise((resolve) => setTimeout(resolve, 10));
const frame3 = renderInstance.lastFrame();
expect(frame3).toContain('3 complete');
// Complete the task
step3Resolve();
await renderInstance.waitUntilExit();
});
});
//# sourceMappingURL=SingleTask.test.js.map