fix: strengthen upgrade tests and show removed packages in UX#3630
fix: strengthen upgrade tests and show removed packages in UX#3630itsjustriley wants to merge 2 commits intomainfrom
Conversation
|
Oxygen deployed a preview of your
Learn more about Hydrogen's GitHub integration. |
Merchants upgrading across versions that remove packages (e.g. the Remix → React Router migration) had no visibility into what was being cleaned up. Now they see a "Removed packages" section in both the upgrade confirmation prompt and the generated instructions markdown. - Extract getAllRemovedPackages() helper to avoid duplicating the removeDependencies + removeDevDependencies concatenation across displayConfirmation and generateUpgradeInstructionsFile - Extend displayConfirmation to show removed packages list when present - Extend generateUpgradeInstructionsFile to include a "## Removed packages" section with backtick-wrapped package names - Export generateUpgradeInstructionsFile for testability Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The upgrade command tests had weak assertions that could pass vacuously, and changelog validation coverage was lost when tests were deleted from upgrade-flow.test.ts. Upgrade test improvements: - Verify renderTasks receives specific task titles instead of just toHaveBeenCalled() - Assert removal task is absent when no deps to remove (direct mock inspection pattern) - Add tests for removed packages display in confirmation prompt and generated markdown instructions New changelog-schema.test.ts (~290 lines): - Validates changelog.json schema: allowed fields, required fields, URL/version/date formats, semver deps, base64 steps, dependenciesMeta - Uses semver.validRange() instead of a permissive regex for version validation — the regex accepted trailing garbage like 1.0.0!@#$% - Precondition assertions on required-field loops prevent vacuous passes (e.g. if all releases lost their features/fixes arrays, the test would still pass green without the precondition) - Optional fields (dependenciesMeta, removeDependencies) intentionally have no preconditions — a valid changelog may have zero instances Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
bbaafb4 to
3432021
Compare
|
CI failure unrelated to this PR |
|
|
||
| const selectedRelease = releases.find( | ||
| (release) => release.version === '2023.10.0', | ||
| ) as (typeof releases)[0]; |
There was a problem hiding this comment.
note: these as type assertions are a pre-existing pattern in this test file (~9 other occurrences). In test code, as for narrowing .find() results is a common and accepted pattern since the test execution itself validates the shape at runtime - if the release doesn't exist, the test will fail. Not worth blocking on given it's consistent with existing test conventions.
| ); | ||
|
|
||
| // With empty removeDependencies, the "Removed packages" section should NOT appear | ||
| expect(info).not.toMatch('Removed packages'); |
There was a problem hiding this comment.
praise: the expect(info).not.toMatch('Removed packages') assertion is a nice addition. This confirms the "Removed packages" section only appears when there are packages to remove - the kind of complementary negative assertion that makes the test suite more robust.
| const tasks = vi.mocked(renderTasks).mock.calls[0]?.[0] ?? []; | ||
| const removalTask = tasks.find( | ||
| (t: {title: string}) => | ||
| t.title === 'Removing deprecated dependencies', | ||
| ); | ||
| expect(removalTask).toBeUndefined(); |
There was a problem hiding this comment.
non-blocking: I think we could use expect.not.arrayContaining instead of reaching into mock.calls directly.
This works, but reaching into mock.calls[0]?.[0] is more fragile than using vitest's built-in matchers. The inline {title: string} type annotation is also a bit noisy. I think this reads more clearly as:
| const tasks = vi.mocked(renderTasks).mock.calls[0]?.[0] ?? []; | |
| const removalTask = tasks.find( | |
| (t: {title: string}) => | |
| t.title === 'Removing deprecated dependencies', | |
| ); | |
| expect(removalTask).toBeUndefined(); | |
| expect(renderTasks).toHaveBeenCalledWith( | |
| expect.not.arrayContaining([ | |
| expect.objectContaining({ title: 'Removing deprecated dependencies' }), | |
| ]), | |
| expect.anything(), | |
| ); |
| '@shopify/cli-hydrogen': patch | ||
| --- | ||
|
|
||
| Show removed packages in the upgrade confirmation prompt and upgrade instructions file so merchants can see which dependencies will be cleaned up during the upgrade. |
There was a problem hiding this comment.
nit:
| Show removed packages in the upgrade confirmation prompt and upgrade instructions file so merchants can see which dependencies will be cleaned up during the upgrade. | |
| Show removed packages in the upgrade confirmation prompt and upgrade instructions file so that it's visible which dependencies will be cleaned up during the upgrade. |
or something like that
| * Generates a markdown file with upgrade instructions | ||
| */ | ||
| async function generateUpgradeInstructionsFile({ | ||
| export async function generateUpgradeInstructionsFile({ |
There was a problem hiding this comment.
question: is this export solely for the new test, or is there a planned consumer?
If it's purely for testability, that's fine - it's consistent with other exports in this file (displayConfirmation, getChangelog, getCumulativeRelease, etc.).
ideally though we would only test exposed interfaces – test coverage for this function would be inside the exported ones
| const {features, fixes} = cumulativeRelease; | ||
| if (features.length || fixes.length) { | ||
| const allRemovedPackages = getAllRemovedPackages(cumulativeRelease); | ||
|
|
There was a problem hiding this comment.
non-blocking: nice catch expanding the guard to include removed packages. This means a release with only removals (no features/fixes) will now show the info block and generate the instructions file - which is the correct behaviour.
worth noting this is a net-new UX path that the current tests don't exercise directly (the test for "displays removed packages" uses CUMULATIVE_RELEASE which IIRC has features/fixes in it).
a quick test with empty features/fixes + non-empty removals would cover the edge case, but not blocking.
| ); | ||
|
|
||
| expect(mdContent).toContain('## Removed packages'); | ||
| expect(mdContent).toContain('- `@remix-run/react`'); |
There was a problem hiding this comment.
non-blocking: there's a test for "markdown includes removed packages when present" but no corresponding negative test for "markdown omits Removed packages when there are none".
The code guard is simple (if (allRemovedPackages.length)), and displayConfirmation already has the negative assertion. would be nice to have the pair complete for generateUpgradeInstructionsFile too.
|
|
||
| for (const release of changelog.releases) { | ||
| // Fail fast on malformed entries rather than silently skipping them | ||
| expect(release).toBeDefined(); |
There was a problem hiding this comment.
non-blocking: the expect(release).toBeDefined(); if (!release) continue; pattern repeats 6 times in this file - worth extracting.
The expect fails fast if undefined and the continue narrows for TypeScript - I get why both are there. But an assertion function would let us drop the guard:
function assertDefined<T>(value: T): asserts value is NonNullable<T> {
expect(value).toBeDefined();
}we have a similar one for playwright!
then each test becomes assertDefined(release) and TypeScript narrows automatically. i dont mind repetition but the type narrowing could be useful too
| ); | ||
| } | ||
|
|
||
| const changelog = await upgradeModule.getChangelog(); |
There was a problem hiding this comment.
non-blocking: getChangelog() is called independently in every test - since this is a static JSON file, a shared beforeAll parse would reduce boilerplate and be slightly faster.
probably carried over from the deleted tests, and porting is an opportunity to tidy up.
WHY are these changes introduced?
Fixes https://github.com/Shopify/developer-tools-team/issues/1113
Fixes https://github.com/Shopify/developer-tools-team/issues/1138
Fixes https://github.com/Shopify/developer-tools-team/issues/1107
Upgrade command tests had weak assertions that could pass vacuously, changelog validation coverage was lost when tests were deleted, and the upgrade UX didn't surface removed packages.
WHAT is this pull request doing?
renderTasksassertions: Verify correct task titles instead of justtoHaveBeenCalled(); verify removal task is absent when no deps to removeupgrade-flow.test.tsinto newchangelog-schema.test.ts— validates allowed fields, URL/version/date format, semver deps, base64 steps, dependenciesMetadisplayConfirmationand markdown list ingenerateUpgradeInstructionsFile. Uses extractedgetAllRemovedPackages()helper to avoid duplication.HOW to test your changes?
npx vitest run packages/cli/src/commands/hydrogen/upgrade.test.ts --pool=forks --poolOptions.forks.singleForknpx vitest run packages/cli/src/commands/hydrogen/changelog-schema.test.ts --pool=forks --poolOptions.forks.singleForkChecklist