認証機能のテストを追加 の変更点

更新


[[プログラミング/svelte]]

* いまさらテストを追加 [#iac1c315]

サーバーの起動とテストの実行を切り離した方が使いやすそう?

#contents

** サーバーの起動 [#r621d0b1]

デバッグサーバーでテストするならこう

 LANG: console
 $ DATABASE_URL="file:test.db" bash -c 'prisma migrate deploy && pnpm dev --port 4173'

ビルド済みでテストするならこう

 LANG: console
 $ DATABASE_URL="file:test.db" bash -c 'prisma migrate deploy && pnpm build && pnpm preview --port 4173'

"prisma migrate dev" が prisma/schema.ts の変更があれば新しい migration を作ってそれをデータベースに適用するのに対して、"prisma migrate deploy" はすでにある prisma/migrations だけを見てデータベースを最新にする。テスト時にやりたいのは migration deploy だ。

これを package.json に入れる方法が良く分からない?

https://qiita.com/riversun/items/d45b26f4a7aad6e51b69 によると、

 LANG: console
 $ pnpm add -D cross-env

しておいて、

package.json
 *    "test": "pnpm test:integration && pnpm test:unit",
 +    "test-dev": "cross-env DATABASE_URL='file:test.db' prisma migrate deploy && cross-env DATABASE_URL='file:test.db' vite dev --port 4173",
 +    "test-preview": "cross-env DATABASE_URL='file:test.db' prisma migrate deploy && cross-env DATABASE_URL='file:test.db' vite build && cross-env DATABASE_URL='file:test.db' vite preview --port 4173",

こうかな?

** テストの実行 [#o5d2e777]

playwright.config.ts
 LANG: ts
   import type { PlaywrightTestConfig } from '@playwright/test';
   
   const config: PlaywrightTestConfig = {
     webServer: {
 *    command: '',  // 'npm run build && npm run preview',
       port: 4173,
       reuseExistingServer: !process.env.CI,
     },
     testDir: 'tests',
     testMatch: /(.+\.)?(test|spec)\.[jt]s/,
   };
 
 export default config;

および

package.json
 +    "test:integration": "cross-env DATABASE_URL='file:test.db' playwright test",
 +    "test:unit": "cross-env DATABASE_URL='file:test.db' vitest"

の設定で

 LANG: console
 $ pnpm test-dev

あるいは

 LANG: console
 $ pnpm test-preview

でサーバーを起動して置き、別のターミナルから

 LANG: console
 $ pnpm test:integration

とすればいい。

・・・と思ったら初期化が足りないというエラーが出た。

 LANG: console
 $ pnpm test:integration
  
  > authtest@0.0.1 test:integration C:\Users\osamu\Desktop\svelte\authtest
  > cross-env DATABASE_URL='file:test.db' playwright test
  
  
  Running 1 test using 1 worker
  
    ✘  1 test.ts:3:1 › index page has expected h1 (26ms)
  
  
    1) test.ts:3:1 › index page has expected h1 ──────────────────────────────────────────────────────
  
      Error: browserType.launch: Executable doesn't exist at C:\Users\osamu\AppData\Local\ms-playwright\chromium-1091\chrome-win\chrome.exe
      ╔═════════════════════════════════════════════════════════════════════════╗
      ║ Looks like Playwright Test or Playwright was just installed or updated. ║
      ║ Please run the following command to download new browsers:              ║
      ║                                                                         ║
      ║     pnpm exec playwright install                                        ║
      ║                                                                         ║
      ║ <3 Playwright Team                                                      ║
      ╚═════════════════════════════════════════════════════════════════════════╝
  
  
  
  
    1 failed
      test.ts:3:1 › index page has expected h1 ───────────────────────────────────────────────────────
   ELIFECYCLE  Command failed with exit code 1.
   
 $ pnpm exec playwright install
  Downloading Chromium 120.0.6099.28 (playwright build v1091) from https://playwright.azureedge.net/builds/chromium/1091/chromium-win64.zip
  122 Mb [====================] 100% 0.0s
  Chromium 120.0.6099.28 (playwright build v1091) downloaded to C:\Users\osamu\AppData\Local\ms-playwright\chromium-1091
  Downloading Firefox 119.0 (playwright build v1429) from https://playwright.azureedge.net/builds/firefox/1429/firefox-win64.zip
  80.5 Mb [====================] 100% 0.0s
  Firefox 119.0 (playwright build v1429) downloaded to C:\Users\osamu\AppData\Local\ms-playwright\firefox-1429
  Downloading Webkit 17.4 (playwright build v1944) from https://playwright.azureedge.net/builds/webkit/1944/webkit-win64.zip
  46.4 Mb [====================] 100% 0.0s
  Webkit 17.4 (playwright build v1944) downloaded to C:\Users\osamu\AppData\Local\ms-playwright\webkit-1944

これで、

 LANG: console
 $ pnpm test:integration
  
  > authtest@0.0.1 test:integration C:\Users\osamu\Desktop\svelte\authtest
  > cross-env DATABASE_URL='file:test.db' playwright test
  
  
  Running 1 test using 1 worker
  
    ✓  1 test.ts:3:1 › index page has expected h1 (404ms)
  
    1 passed (2.5s)

テストが走るようになった。

 LANG: console
 $ git add . && git commit -m "テスト環境を整えた"
  package.json 98ms (unchanged)
  
  🌼   daisyUI 4.4.14
  ├─ ✔︎ 1 theme added             https://daisyui.com/docs/themes
  ╰─ ❤︎ Support daisyUI project:  https://opencollective.com/daisyui
  
  playwright.config.ts 1636ms
  [master 169df63] テスト環境を整えた
   3 files changed, 19 insertions(+), 4 deletions(-)

** テストを書く [#i8e7b89e]

https://playwright.dev/docs/api/class-test

例えばこんな風に書ける。

 LANG: ts
 test('/signup へフォームを送信', async ({ page }) => {
  await page.goto('/signup', {waitUntil: 'load'});
 
  await page.locator('input[name="email"]').fill('test@example.com');
  Promise.all([
    page.locator('input[name="email"]').press('Enter'),
    page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'})
  ]);
  await expect(page.getByRole('heading', { name: 'Welcome to Svelte' })).toBeVisible();
 });

でもこれを、

 LANG: ts
   await page.locator('input[name="email"]').fill('test@example.com');
   await page.locator('form button').click();

とすると email フィールドへの入力が送信データに反映されないケースがあったり(svelte による reactivity が発揮される前にフォームが送信されてしまうため?)、

なぜか fill を2回繰り返さないと書き込み自体を行えないケースがあったり、

 LANG: ts
   await page.locator('input[name="email"]').fill('test@example.com');
   await page.locator('input[name="email"]').fill('test@example.com'); // 念のためもう一度 orz

なかなかに落とし穴も多そうな雰囲気だ?

- locator と WaitForSelector, $, $$ の違い https://qiita.com/ko-he-8/items/85116e1d99ed4b176657
- npm playwright codegen で操作手順の記録ができる https://playwright.dev/docs/codegen-intro
- 以前 --ui オプションで GUI が立ち上がるのを確認した記録があるのだけれど、このプロジェクトにインストールされているバージョンだとそんなオプションないと言われる?
- https://www.summerbud.org/dev-notes/playwright-tips-that-will-make-your-life-easier 
-- .fill などは readonly や disable の間はそうじゃなくなるまで待つらしい
-- race condition を避けるために Promise.all を使う
- DEBUG=pw:api pnpm test:integration で verbose な出力が得られる
- テストは test( を test.skip( にするとスキップできる

** 長大なテストになった [#ae5325df]

tests/auth.test.ts
 LANG: ts
 import { expect, test, type Page, type TestInfo } from '@playwright/test';
 import type { SendMailOptions } from "nodemailer";
 import * as fs from "fs";
 import { db } from '../src/lib/server/db.js';
 
 test.setTimeout(5000);
 
 let testId = 0;
 let screenshotId = 0;
 let screenshotTitle = "";
 async function screenshot(page: Page, info: TestInfo) {
   if(screenshotTitle != info.title) {
     testId++;
     screenshotTitle = info.title;
     screenshotId = 0;
   }
   await page.screenshot({path: `test-results/ss${testId.toString().padStart(4, '0')}-${info.title.replaceAll("/","_")}${++screenshotId}.png`})
 }
 
 test('データベースをクリアする', async () => {
   await db.emailVerification.deleteMany();
   await db.user.deleteMany();
 
   expect(await db.emailVerification.count()).toBe(0);
   expect(await db.user.count()).toBe(0);
 });
 
 test('インデックスページを表示できる', async ({ page }, info) => {
   await page.goto('/', {waitUntil: 'load'});
   await expect(page.getByRole('heading', { name: 'Welcome to Svelte' })).toBeVisible();
   await screenshot(page, info);
 });
 
 test('/account/new のメールアドレスの検証', async ({ page }, info) => {
   await page.goto('/account/new', {waitUntil: 'load'});
   
   await page.locator('input[name="email"]').fill('');
   await page.locator('input[name="email"]').press('Enter');
   await page.locator('form button').isEnabled();
   await expect(page.locator('input[name="email"]+label>.text-error')).toContainText('メールアドレスが不正です');
 
   await page.locator('input[name="email"]').fill('abcd@');
   await page.locator('input[name="email"]').press('Enter');
   await page.locator('form button').isEnabled();
   await expect(page.locator('input[name="email"]+label>.text-error')).toContainText('メールアドレスが不正です');
 
   await page.locator('input[name="email"]').fill('@abcc');
   await page.locator('input[name="email"]').press('Enter');
   await page.locator('form button').isEnabled();
   await expect(page.locator('input[name="email"]+label>.text-error')).toContainText('メールアドレスが不正です');
 
   await screenshot(page, info);
 });
 
 test('メールアドレスを入力する /account/new', async ({ page }, info) => {
   await page.goto('/account/new', {waitUntil: 'load'});
 
   await page.locator('input[name="email"]').fill('test@example.com');
   await page.locator('input[name="email"]').press('Enter');
 
   await expect(page.locator('form button')).not.toBeVisible();
   await page.waitForLoadState('load');
   await expect(page.url()).toBe('http://localhost:4173/');
 
   await expect(page.locator('.toaster .message')).toHaveText('メールを送信しました')
   await page.waitForTimeout(200);
 
   await screenshot(page, info);
 });
 
 test('連続して送ろうとするとエラーになる', async ({ page }, info) => {
   await page.goto('/account/new', {waitUntil: 'load'});
 
   await page.locator('input[name="email"]').fill('test@example.com');
   await page.locator('input[name="email"]').press('Enter');
 
   await expect(page.locator('.toaster .message').nth(0)).toHaveText('先ほどメールを送信しましたのでしばらく経ってから試してください')
   await screenshot(page, info);
 });
 
 test('サインアップを続ける', async ({ page }, info) => {
   const mailOptions = JSON.parse(fs.readFileSync('test-results/mailOptions.txt', 'utf-8')) as SendMailOptions;
   await expect(mailOptions.text).toContain('http');
   await page.goto((mailOptions.text as string).split(/\n/)[1], {waitUntil: 'load'});
   await screenshot(page, info);
 
   await page.click('form button');
   await page.locator('form button').isEnabled();
   await expect(page.locator('input[name="name"]+label>.text-error')).toContainText('文字');
 
   await page.locator('input[name="name"]').fill('First Family');
   await page.locator('input[name="name"]').fill('First Family'); // 1回だと失敗することがある???
   await page.locator('input[name="name"]').press('Enter');
   await screenshot(page, info);
   await page.locator('form button').isEnabled();
   await screenshot(page, info);
   await expect(page.locator('input[name="name"]+label>.text-error')).not.toBeVisible();
   await expect(page.locator('input[name="password"]+label>.text-error')).toContainText('文字');
 
   await page.fill('input[name="password"]', 'aaaaaaaa');
   await page.click('form button');
   await page.locator('form button').isEnabled();
   await expect(page.locator('input[name="password"]+label>.text-error')).toContainText('文字');
 
   await page.fill('input[name="password"]', 'Aa1aaaaa');
   await page.click('form button');
   await page.locator('form button').isEnabled();
   await expect(page.locator('input[name="password"]+label>.text-error')).not.toBeVisible();
   await expect(page.locator('input[name="confirm"]+label>.text-error')).toContainText('一致しません');
 
   await page.fill('input[name="confirm"]', 'Aa1aaaaa');
   await screenshot(page, info);
 
   await page.click('form button');
 
   await page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'});
   await expect(page.locator('.toaster .message').nth(0)).toHaveText('サインアップ&ログインしました')
   await page.waitForTimeout(200);
   await screenshot(page, info);
 
   // ログアウトする
   await logout(page, info);
 
   await page.waitForTimeout(200);
   await screenshot(page, info);
 });
 
 async function logout(page: Page, info: TestInfo) {
   Promise.all([
     page.goto('/session/delete', {waitUntil: 'load'}),
     page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'})
   ]);
   await expect(page.locator('.toaster .message').nth(0)).toHaveText('ログアウトしました')
   await page.waitForTimeout(200);
   await screenshot(page, info);
 }
 
 async function login(page: Page, email='test@example.com', password='Aa1aaaaa') {
   await page.goto('/session/new', {waitUntil: 'load'});
   await page.locator('input[name="email"]').fill(email);
   await page.locator('input[name="password"]').fill(password);
   await Promise.all([
     page.locator('input[name="password"]').press('Enter'),
     page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'})
   ]);
 
   await expect(page.locator('.toaster .message').nth(0)).toHaveText('ログインしました');
 }
 
 test('ログインしていないのにログアウトする', async ({ page }, info) => {
   await page.goto('/session/delete', {waitUntil: 'load'});
 
   await page.waitForNavigation({url: 'http://localhost:4173/session/new', waitUntil: 'load'});
   await expect(page.locator('.toaster .message').nth(0)).toHaveText('ログインユーザーのみアクセスできます')
   await page.waitForTimeout(200);
   await screenshot(page, info);
 });
 
 test('ログインする', async ({ page }, info) => {
   // ログインする
 
   await page.goto('/session/new', {waitUntil: 'load'});
   await screenshot(page, info);
 
   await page.locator('input[name="email"]').press('Enter');
   await page.locator('form button').isEnabled();
   await expect(page.locator('input[name="email"]+label>.text-error')).toContainText('入力して下さい');
 
   await page.locator('input[name="email"]').fill('test@example.com');
   await page.locator('input[name="email"]').fill('test@example.com');
   await page.locator('input[name="email"]').press('Enter');
   await page.locator('form button').isEnabled();
   await expect(page.locator('input[name="password"]+label>.text-error')).toContainText('入力して下さい');
 
   await page.locator('input[name="password"]').fill('Aa1aaaaa');
   await screenshot(page, info);
   await page.locator('input[name="password"]').press('Enter');
   await screenshot(page, info);
   await page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'});
   await expect(page.locator('.toaster .message').nth(0)).toHaveText('ログインしました');
   await page.waitForTimeout(200);
   await screenshot(page, info);
 });
 
 test('名前やパスワードを変更する', async ({ page }, info)=> {
   // ログイン
   await login(page);
 
   // 名前・パスワードを変更する(実際にはしない)
   await page.goto('/account/edit', {waitUntil: 'load'});
   await screenshot(page, info);
 
   await page.locator('input[name="name"]').press('Enter');
   await page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'});
   await expect(page.locator('.toaster .message').nth(0)).toContainText('変更されませんでした');
   await page.waitForTimeout(200);
   await screenshot(page, info);
   
   // 名前を変更する
   await page.goto('/account/edit', {waitUntil: 'load'});
 
   await page.locator('input[name="name"]').fill('First Middle Family');
   await page.locator('input[name="name"]').fill('First Middle Family');
   await page.locator('input[name="name"]').press('Enter');
   await page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'});
   await expect(page.locator('.toaster .message').nth(0)).toContainText('名前が変更されました');
   await page.waitForTimeout(200);
   await screenshot(page, info);
   
   // パスワードを変更する
   await page.goto('/account/edit', {waitUntil: 'load'});
   await screenshot(page, info);
 
   await page.locator('input[name="password"]').fill('Aa1aaaa');
   await page.locator('input[name="password"]').fill('Aa1aaaa');
   await page.locator('input[name="password"]').press('Enter');
   await page.locator('form button').isEnabled();
   await expect(page.locator('input[name="password"]+label>.text-error')).toContainText('文字');
   
   await page.locator('input[name="password"]').fill('Aa1aaaab');
   await page.locator('input[name="password"]').fill('Aa1aaaab');
   await page.locator('input[name="password"]').press('Enter');
   await page.locator('form button').isEnabled();
   await expect(page.locator('input[name="confirm"]+label>.text-error')).toContainText('一致しません');
   
   await page.locator('input[name="confirm"]').fill('Aa1aaaab');
   await page.locator('input[name="confirm"]').fill('Aa1aaaab');
   await page.locator('input[name="confirm"]').press('Enter');
   
   await page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'});
   await expect(page.locator('.toaster .message').nth(0)).toContainText('パスワードが変更されました');
   await page.waitForTimeout(200);
   await screenshot(page, info);
   
   // 名前とパスワードを変更する
   await page.goto('/account/edit', {waitUntil: 'load'});
   await screenshot(page, info);
   await expect(page.locator('input[name="name"]')).toHaveValue('First Middle Family');
 
   await page.locator('input[name="name"]').fill('First Family');
   await page.locator('input[name="name"]').fill('First Family');
  
   await page.locator('input[name="password"]').fill('Aa1aaaaa');
   await page.locator('input[name="password"]').fill('Aa1aaaaa');
   
   await page.locator('input[name="confirm"]').fill('Aa1aaaaa');
   await page.locator('input[name="confirm"]').fill('Aa1aaaaa');
   await screenshot(page, info);
 
   await page.locator('input[name="confirm"]').press('Enter');
   
   await page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'});
   await expect(page.locator('.toaster .message').nth(0)).toContainText('名前とパスワードが変更されました');
   await page.waitForTimeout(200);
   await screenshot(page, info);
   
   await logout(page, info);
 });
 
 test('メールアドレスを変更する', async ({ page }, info)=> {
   // ログイン
   await login(page);
 
   await page.goto('/account/email', {waitUntil: 'load'});
   await screenshot(page, info);
 
   await page.locator('input[name="email"]').press('Enter');
   await expect(page.locator('input[name="email"]+label>.text-error')).toContainText('不正です');
 
   await page.locator('input[name="email"]').fill('changed@example.com');
   await page.locator('input[name="email"]').press('Enter');
   
   await page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'});
   await expect(page.locator('.toaster .message').nth(0)).toContainText('送信しました');
   await page.waitForTimeout(200);
   await screenshot(page, info);
 
   const mailOptions = JSON.parse(fs.readFileSync('test-results/mailOptions.txt', 'utf-8')) as SendMailOptions;
   await expect(mailOptions.text).toContain('http');
   await page.goto((mailOptions.text as string).split(/\n/)[1], {waitUntil: 'load'});
   await screenshot(page, info);
 
   await expect(page.locator('input[name="name"]')).toHaveValue('First Family');
   await expect(page.locator('input[name="email-old"]')).toHaveValue('test@example.com');
   await expect(page.locator('input[name="email-new"]')).toHaveValue('changed@example.com');
 
   Promise.all([
     page.locator('form button').click(),
     page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'})
   ]);
 
   await expect(page.locator('.toaster .message').nth(0)).toContainText('変更しました');
   await page.waitForTimeout(200);
   await screenshot(page, info);
 
   // ログアウト
   await logout(page, info);
 
 });
 
 test('新しいメールアドレスでログインしてから元に戻す', async ({ page }, info)=> {
   
   await login(page, 'changed@example.com');
 
   await page.goto('/account/email', {waitUntil: 'load'});
   await page.locator('input[name="email"]').fill('test@example.com');
   await page.locator('input[name="email"]').fill('test@example.com');
   await Promise.all([
     page.locator('input[name="email"]').press('Enter'),
     page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'})
   ]);
   await expect(page.locator('.toaster .message').nth(0)).toContainText('送信しました');
 
   const mailOptions2 = JSON.parse(fs.readFileSync('test-results/mailOptions.txt', 'utf-8')) as SendMailOptions;
   await expect(mailOptions2.text).toContain('http');
   await page.goto((mailOptions2.text as string).split(/\n/)[1], {waitUntil: 'load'});
   await page.locator('form button').click();
 
   await page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'});
   await expect(page.locator('.toaster .message').nth(0)).toContainText('変更しました');
 
   await logout(page, info);
 });
 
 test('パスワードのリセットを行う', async ({ page }, info) => {
   await page.goto('/account/reset', {waitUntil: 'load'});
   await screenshot(page, info);
 
   await page.locator('input[name="email"]').press('Enter');
   await expect(page.locator('input[name="email"]+label>.text-error')).toContainText('不正です');
 
   await page.locator('input[name="email"]').fill('notfound@example.com');
   await page.locator('input[name="email"]').press('Enter');
   await expect(page.locator('input[name="email"]+label>.text-error')).toContainText('登録されていません');
   
   await page.locator('input[name="email"]').fill('test@example.com');
   Promise.all([
     page.locator('input[name="email"]').press('Enter'),
     page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'})
   ]);
   
   await expect(page.locator('.toaster .message').nth(0)).toContainText('送信しました');
   await page.waitForTimeout(200);
   await screenshot(page, info);
 
   const mailOptions = JSON.parse(fs.readFileSync('test-results/mailOptions.txt', 'utf-8')) as SendMailOptions;
   await expect(mailOptions.text).toContain('http');
   await page.goto((mailOptions.text as string).split(/\n/)[1], {waitUntil: 'load'});
   await screenshot(page, info);
 
   await expect(page.locator('input[name="name"]')).toHaveValue('First Family');
   await expect(page.locator('input[name="email"]')).toHaveValue('test@example.com');
 
   await page.locator('input[name="password"]').press('Enter');
   await expect(page.locator('input[name="password"]+label>.text-error')).toContainText('文字');
 
   await page.locator('input[name="password"]').fill('Aa1aaaaa');
   await page.locator('input[name="confirm"]').fill('Aa1aaaaa');
   await screenshot(page, info);
   await page.locator('input[name="confirm"]').press('Enter');
 
   await page.waitForNavigation({url: 'http://localhost:4173/', waitUntil: 'load'});
   await expect(page.locator('.toaster .message').nth(0)).toContainText('パスワードを変更してログインしました');
   await page.waitForTimeout(200);
   await screenshot(page, info);
 
 });

 LANG: console
 $ pnpm test:integration
 
 > authtest@0.0.1 test:integration C:\Users\osamu\Desktop\svelte\authtest
 > cross-env DATABASE_URL='file:test.db'; playwright test
 
 Running 12 tests using 1 worker
 
   ✓  1 auth.test.ts:13:1 › データベースをクリアする (44ms)
   ✓  2 auth.test.ts:21:1 › インデックスページを表示できる (351ms)
   ✓  3 auth.test.ts:27:1 › /signup のメールアドレスの検証 (580ms)
   ✓  4 auth.test.ts:48:1 › メールアドレスを入力する /signup (749ms)
   ✓  5 auth.test.ts:64:1 › 連続して送ろうとするとエラーになる (509ms)
   ✓  6 auth.test.ts:75:1 › サインアップを続ける (2s)
   ✓  7 auth.test.ts:146:1 › ログインしていないのにログアウトする (569ms)
   ✓  8 auth.test.ts:155:1 › ログインする (1s)
   ✓  9 auth.test.ts:181:1 › 名前やパスワードを変更する (4s)
   ✓  10 auth.test.ts:256:1 › メールアドレスを変更する (2s)
   ✓  11 auth.test.ts:297:1 › 新しいメールアドレスでログインしてから元に戻す (1s)
   ✓  12 auth.test.ts:321:1 › パスワードのリセットを行う (2s)
 
   Slow test file: auth.test.ts (16s)
   Consider splitting slow test files to speed up parallel execution
 
   12 passed (17s)

 LANG: console
 $ git add . && git commit -m "認証部分のテストを追加"

* Action を satisfies で指定するようにした [#fef4f485]

src/routes/+page.server.ts
 LANG: ts
 - export const load: PageServerLoad = async ({ locals }) => {
 -   return { user: locals.session?.user };
 - };
 + export const load = (async ({ locals }) => {
 +   return { user: locals.session?.user };
 + }) satisfies PageServerLoad;

こういうやつ。

っていうか、上のやつは Action じゃなく PageServerLoad がおかしなところについていた。

 LANG: console
 $ git add . && git commit -m "Action を satisfies で指定するようにした"
  
  🌼   daisyUI 4.4.14
  ├─ ✔︎ 1 theme added             https://daisyui.com/docs/themes
  ╰─ ★ Star daisyUI on GitHub     https://github.com/saadeghi/daisyui
  
  src/routes/+page.server.ts 1646ms (unchanged)
  src/routes/account/(login)/edit/+page.server.ts 45ms (unchanged)
  src/routes/account/(logout)/new/[token]/+page.server.ts 34ms (unchanged)
  src/routes/account/[purpose=emailVerificationPurpose]/+page.server.ts 34ms (unchanged)
  src/routes/session/(logout)/new/+page.server.ts 16ms (unchanged)
  [master 2ea29a3] Action を satisfies で指定するようにした
   5 files changed, 10 insertions(+), 10 deletions(-)

 $

Counter: 239 (from 2010/06/03), today: 1, yesterday: 1