Skip to content

Commit

Permalink
Merge pull request #10393 from Turbo87/msw
Browse files Browse the repository at this point in the history
Migrate from `ember-cli-mirage` to `msw` and `@mswjs/data`
  • Loading branch information
Turbo87 authored Jan 31, 2025
2 parents 64c6940 + a328a12 commit ae8195c
Show file tree
Hide file tree
Showing 368 changed files with 9,397 additions and 7,595 deletions.
9 changes: 0 additions & 9 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,6 @@ module.exports = {
},
},

// mirage files
{
files: ['mirage/**/*.js'],
rules: {
// disabled because of different `.find()` meaning
'unicorn/no-array-callback-reference': 'off',
},
},

// node files
{
files: [
Expand Down
26 changes: 25 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
files_ignore: |
app/**
e2e/**
mirage/**
packages/**
public/**
tests/**
.eslintrc
Expand Down Expand Up @@ -239,6 +239,30 @@ jobs:
- if: github.repository != 'rust-lang/crates.io'
run: pnpm test-coverage

msw-test:
name: Frontend / Test (@crates-io/msw)
runs-on: ubuntu-24.04
needs: [changed-files]
if: needs.changed-files.outputs.non-rust == 'true'

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:
version: ${{ env.PNPM_VERSION }}

- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with:
cache: pnpm
node-version-file: package.json

- run: pnpm install

- run: pnpm --filter "@crates-io/msw" test

e2e-test:
name: Frontend / Test (playwright)
runs-on: ubuntu-24.04
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/tmp

# dependencies
/node_modules
node_modules/
/bower_components
package-lock.json
yarn.lock
Expand Down
2 changes: 1 addition & 1 deletion app/templates/dashboard.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
</h2>

<div local-class="feed">
<ul local-class="feed-list">
<ul local-class="feed-list" data-test-feed-list>
{{#each this.myFeed as |version|}}
<li local-class="feed-row">
<LinkTo @route="crate.version" @models={{array version.crateName version.num}}>
Expand Down
2 changes: 0 additions & 2 deletions config/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ module.exports = function (environment) {

if (environment === 'production') {
// here you can enable a production-specific feature
delete ENV['ember-cli-mirage'];

ENV.sentry = {
dsn: process.env.SENTRY_DSN_WEB,
};
Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ These files have to do with the frontend:
- `.ember-cli` - Settings for the `ember` command line interface
- `ember-cli-build.js` - Contains the build specification for Broccoli
- `.eslintrc.js` - Defines Javascript coding style guidelines (enforced during CI???)
- `mirage/` - A mock backend used during development and testing
- `node_modules/` - npm dependencies - (ignored in `.gitignore`)
- `packages/crates-io-msw` - A mock backend used for testing
- `package.json` - Defines the npm package and its dependencies
- `package-lock.json` - Locks dependencies to specific versions providing consistency across
development and deployment
Expand Down
18 changes: 6 additions & 12 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,18 +134,12 @@ To build and serve the frontend assets, use the command `pnpm start`. There
are variations on this command that change which backend your frontend tries to
talk to:

| Command | Backend | Use case |
| ----------------------------------------- | --------------------------------------------- | ------------------------------------------------------- |
| `pnpm start:live` | <https://crates.io> | Testing UI changes with the full live site's data |
| `pnpm start:staging` | <https://staging-crates-io.herokuapp.com> | Testing UI changes with a smaller set of realistic data |
| `pnpm start` | Static fixture test data in `mirage/fixtures` | Setting up particular situations, see note |
| `pnpm start:local` | Backend server running locally | See the Working on the backend section for setup |
| `pnpm start -- --proxy https://crates.io` | Whatever is specified in `--proxy` arg | If your use case is not covered here |

> Note: If you want to set up a particular situation, you can edit the fixture
> data used for tests in `mirage/fixtures`. The fixture data does not currently
> contain JSON needed to support every page, so some pages might not load
> correctly.
| Command | Backend | Use case |
| ----------------------------------------- | ----------------------------------------- | ------------------------------------------------------- |
| `pnpm start:live` | <https://crates.io> | Testing UI changes with the full live site's data |
| `pnpm start:staging` | <https://staging-crates-io.herokuapp.com> | Testing UI changes with a smaller set of realistic data |
| `pnpm start:local` | Backend server running locally | See the Working on the backend section for setup |
| `pnpm start -- --proxy https://crates.io` | Whatever is specified in `--proxy` arg | If your use case is not covered here |

#### Running the frontend tests

Expand Down
86 changes: 40 additions & 46 deletions e2e/acceptance/api-token.spec.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
import { test, expect } from '@/e2e/helper';
import { expect, test } from '@/e2e/helper';
import { http, HttpResponse } from 'msw';

test.describe('Acceptance | api-tokens', { tag: '@acceptance' }, () => {
test.beforeEach(async ({ mirage }) => {
await mirage.addHook(server => {
let user = server.create('user', {
login: 'johnnydee',
name: 'John Doe',
email: '[email protected]',
avatar: 'https://avatars2.githubusercontent.com/u/1234567?v=4',
});
server.create('api-token', {
user,
name: 'BAR',
createdAt: '2017-11-19T17:59:22',
lastUsedAt: null,
expiredAt: '2017-12-19T17:59:22',
});

server.create('api-token', {
user,
name: 'recently expired',
createdAt: '2017-08-01T12:34:56',
lastUsedAt: '2017-11-02T01:45:14',
expiredAt: '2017-11-19T17:59:22',
});
server.create('api-token', {
user,
name: 'foo',
createdAt: '2017-08-01T12:34:56',
lastUsedAt: '2017-11-02T01:45:14',
});

globalThis.authenticateAs(user);
test.beforeEach(async ({ msw }) => {
let user = msw.db.user.create({
login: 'johnnydee',
name: 'John Doe',
email: '[email protected]',
avatar: 'https://avatars2.githubusercontent.com/u/1234567?v=4',
});
msw.db.apiToken.create({
user,
name: 'BAR',
createdAt: '2017-11-19T17:59:22',
lastUsedAt: null,
expiredAt: '2017-12-19T17:59:22',
});

msw.db.apiToken.create({
user,
name: 'recently expired',
createdAt: '2017-08-01T12:34:56',
lastUsedAt: '2017-11-02T01:45:14',
expiredAt: '2017-11-19T17:59:22',
});
msw.db.apiToken.create({
user,
name: 'foo',
createdAt: '2017-08-01T12:34:56',
lastUsedAt: '2017-11-02T01:45:14',
});

await msw.authenticateAs(user);
});

test('/me is showing the list of active API tokens', async ({ page }) => {
Expand Down Expand Up @@ -72,16 +71,13 @@ test.describe('Acceptance | api-tokens', { tag: '@acceptance' }, () => {
await expect(row3.locator('[data-test-token]')).toHaveCount(0);
});

test('API tokens can be revoked', async ({ page }) => {
test('API tokens can be revoked', async ({ page, msw }) => {
await page.goto('/settings/tokens');
await expect(page).toHaveURL('/settings/tokens');
await expect(page.locator('[data-test-api-token]')).toHaveCount(3);

await page.click('[data-test-api-token="1"] [data-test-revoke-token-button]');
expect(
await page.evaluate(() => server.schema['apiTokens'].all().length),
'API token has been deleted from the backend database',
).toBe(2);
expect(msw.db.apiToken.findMany({}).length, 'API token has been deleted from the backend database').toBe(2);

await expect(page.locator('[data-test-api-token]')).toHaveCount(2);
await expect(page.locator('[data-test-api-token="2"]')).toBeVisible();
Expand All @@ -97,12 +93,10 @@ test.describe('Acceptance | api-tokens', { tag: '@acceptance' }, () => {
await expect(page).toHaveURL('/settings/tokens/new?from=1');
});

test('failed API tokens revocation shows an error', async ({ page, mirage }) => {
await mirage.addHook(server => {
server.delete('/api/v1/me/tokens/:id', {}, 500);
});
test('failed API tokens revocation shows an error', async ({ page, msw }) => {
await msw.worker.use(http.delete('/api/v1/me/tokens/:id', () => HttpResponse.json({}, { status: 500 })));

await mirage.page.goto('/settings/tokens');
await page.goto('/settings/tokens');
await expect(page).toHaveURL('/settings/tokens');
await expect(page.locator('[data-test-api-token]')).toHaveCount(3);

Expand All @@ -115,7 +109,7 @@ test.describe('Acceptance | api-tokens', { tag: '@acceptance' }, () => {
);
});

test('new API tokens can be created', async ({ page, percy }) => {
test('new API tokens can be created', async ({ page, percy, msw }) => {
await page.goto('/settings/tokens');
await expect(page).toHaveURL('/settings/tokens');
await expect(page.locator('[data-test-api-token]')).toHaveCount(3);
Expand All @@ -129,7 +123,7 @@ test.describe('Acceptance | api-tokens', { tag: '@acceptance' }, () => {

await page.click('[data-test-generate]');

let token = await page.evaluate(() => server.schema['apiTokens'].findBy({ name: 'the new token' })?.token);
let token = msw.db.apiToken.findFirst({ where: { name: { equals: 'the new token' } } })?.token;
expect(token, 'API token has been created in the backend database').toBeTruthy();

await expect(page.locator('[data-test-api-token="4"] [data-test-name]')).toHaveText('the new token');
Expand All @@ -140,14 +134,14 @@ test.describe('Acceptance | api-tokens', { tag: '@acceptance' }, () => {
await expect(page.locator('[data-test-token]')).toHaveText(token);
});

test('API tokens are only visible in plaintext until the page is left', async ({ page }) => {
test('API tokens are only visible in plaintext until the page is left', async ({ page, msw }) => {
await page.goto('/settings/tokens');
await page.click('[data-test-new-token-button]');
await page.fill('[data-test-name]', 'the new token');
await page.click('[data-test-scope="publish-update"]');
await page.click('[data-test-generate]');

let token = await page.evaluate(() => server.schema['apiTokens'].findBy({ name: 'the new token' })?.token);
let token = msw.db.apiToken.findFirst({ where: { name: { equals: 'the new token' } } })?.token;
await expect(page.locator('[data-test-token]')).toHaveText(token);

// leave the API tokens page
Expand Down
38 changes: 15 additions & 23 deletions e2e/acceptance/categories.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { test, expect } from '@/e2e/helper';
import { expect, test } from '@/e2e/helper';

test.describe('Acceptance | categories', { tag: '@acceptance' }, () => {
test('listing categories', async ({ page, mirage, percy, a11y }) => {
await mirage.addHook(server => {
server.create('category', { category: 'API bindings' });
server.create('category', { category: 'Algorithms' });
server.createList('crate', 1, { categoryIds: ['algorithms'] });
server.create('category', { category: 'Asynchronous' });
server.createList('crate', 15, { categoryIds: ['asynchronous'] });
server.create('category', { category: 'Everything', crates_cnt: 1234 });
});
test('listing categories', async ({ page, msw, percy, a11y }) => {
msw.db.category.create({ category: 'API bindings' });
let algos = msw.db.category.create({ category: 'Algorithms' });
msw.db.crate.create({ categories: [algos] });
let async = msw.db.category.create({ category: 'Asynchronous' });
Array.from({ length: 15 }).forEach(() => msw.db.crate.create({ categories: [async] }));
msw.db.category.create({ category: 'Everything', crates_cnt: 1234 });

await page.goto('/categories');

Expand All @@ -22,10 +20,8 @@ test.describe('Acceptance | categories', { tag: '@acceptance' }, () => {
await a11y.audit();
});

test('category/:category_id index default sort is recent-downloads', async ({ page, mirage, percy, a11y }) => {
await mirage.addHook(server => {
server.create('category', { category: 'Algorithms' });
});
test('category/:category_id index default sort is recent-downloads', async ({ page, msw, percy, a11y }) => {
msw.db.category.create({ category: 'Algorithms' });
await page.goto('/categories/algorithms');

await expect(page.locator('[data-test-category-sort] [data-test-current-order]')).toHaveText('Recent Downloads');
Expand All @@ -34,11 +30,9 @@ test.describe('Acceptance | categories', { tag: '@acceptance' }, () => {
await a11y.audit();
});

test('listing category slugs', async ({ page, mirage }) => {
await mirage.addHook(server => {
server.create('category', { category: 'Algorithms', description: 'Crates for algorithms' });
server.create('category', { category: 'Asynchronous', description: 'Async crates' });
});
test('listing category slugs', async ({ page, msw }) => {
msw.db.category.create({ category: 'Algorithms', description: 'Crates for algorithms' });
msw.db.category.create({ category: 'Asynchronous', description: 'Async crates' });
await page.goto('/category_slugs');

await expect(page.locator('[data-test-category-slug="algorithms"]')).toHaveText('algorithms');
Expand All @@ -50,10 +44,8 @@ test.describe('Acceptance | categories', { tag: '@acceptance' }, () => {

test.describe('Acceptance | categories (locale: de)', { tag: '@acceptance' }, () => {
test.use({ locale: 'de' });
test('listing categories', async ({ page, mirage }) => {
await mirage.addHook(server => {
server.create('category', { category: 'Everything', crates_cnt: 1234 });
});
test('listing categories', async ({ page, msw }) => {
msw.db.category.create({ category: 'Everything', crates_cnt: 1234 });
await page.goto('categories');

await expect(page.locator('[data-test-category="everything"] [data-test-crate-count]')).toHaveText('1.234 crates');
Expand Down
16 changes: 7 additions & 9 deletions e2e/acceptance/crate-deletion.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { expect, test } from '@/e2e/helper';

test.describe('Acceptance | crate deletion', { tag: '@acceptance' }, () => {
test('happy path', async ({ page, mirage }) => {
await mirage.addHook(server => {
let user = server.create('user');
authenticateAs(user);
test('happy path', async ({ page, msw }) => {
let user = msw.db.user.create();
await msw.authenticateAs(user);

let crate = server.create('crate', { name: 'foo' });
server.create('version', { crate });
server.create('crate-ownership', { crate, user });
});
let crate = msw.db.crate.create({ name: 'foo' });
msw.db.version.create({ crate });
msw.db.crateOwnership.create({ crate, user });

await page.goto('/crates/foo');
await expect(page).toHaveURL('/crates/foo');
Expand All @@ -34,7 +32,7 @@ test.describe('Acceptance | crate deletion', { tag: '@acceptance' }, () => {
let message = 'Crate foo has been successfully deleted.';
await expect(page.locator('[data-test-notification-message="success"]')).toHaveText(message);

let crate = await page.evaluate(() => server.schema.crates.findBy({ name: 'foo' }));
crate = msw.db.crate.findFirst({ where: { name: { equals: 'foo' } } });
expect(crate).toBeNull();
});
});
Loading

0 comments on commit ae8195c

Please sign in to comment.