[
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: Run Test\n\non: push\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 18\n      - name: Install dependencies\n        run: npm ci\n      - name: Jest unit test\n        run: npm test\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.DS_Store\ncoverage\n__reports__\n"
  },
  {
    "path": ".storybook/main.js",
    "content": "module.exports = {\n  stories: [\"../src/**/*.stories.@(js|jsx|ts|tsx)\"],\n  addons: [\n    \"@storybook/addon-links\",\n    \"@storybook/addon-essentials\",\n    \"@storybook/addon-interactions\",\n    \"@storybook/addon-a11y\",\n  ],\n  framework: \"@storybook/react\",\n  core: {\n    builder: \"@storybook/builder-webpack5\",\n  },\n}"
  },
  {
    "path": ".storybook/preview.js",
    "content": "export const parameters = {\n  actions: { argTypesRegex: \"^on[A-Z].*\" },\n  controls: {\n    matchers: {\n      color: /(background|color)$/i,\n      date: /Date$/,\n    },\n  },\n}"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 frontend-testing-book\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# フロントエンドのテスト入門\n\nこれは「フロントエンド開発のためのテスト入門」の第３章〜第６章で解説する、トレーニングリポジトリです。対象読者は、初めてフロントエンドのテストに取り組まれる方を想定しており、Jestの基本的な使用方法から解説しています。\n\n- 第３章 はじめの単体テスト\n- 第４章 モック\n- 第５章 UI コンポーネントテスト\n- 第６章 カバレッジレポートの読み方\n\n## 第３章 はじめの単体テスト\n\nJest を使用したテストコードを解説します。はじめてテストに取り組まれる方にとって、最適な内容です。\n\n【サンプルコード】`src/03/**/*.test.ts`\n\n```\n$ npm test\n```\n\n## 第４章 モック\n\nテストを実施するうえで、必要不可欠な「モック」について解説します。Jest は標準でモック機能を備えています。\n\n【サンプルコード】`src/04/**/*.test.ts`\n\n```\n$ npm test\n```\n\n## 第５章 UI コンポーネントテスト\n\nNode.js には DOM API がありませんが、jsdom という DOM API をシュミレートするテスト環境を用意することで、実際のブラウザでテストするのと同じようなテストが「高速に」実施できます。Testing Library は、そのテスト環境において、UIコンポーネントテストをサポートするライブラリです。\n\n【サンプルコード】`src/05/**/*.test.tsx`\n\n```\n$ npm test\n```\n\n## 第６章 カバレッジレポートの読み方\n\n単体テスト・結合テストに慣れたら、カバレッジレポートを確認して、不足しているテストを見つけてみましょう。カバレッジレポートを確認することで、テストコードを書くポイントがはっきりします。\n\n【サンプルコード】`src/06/**/*.test.{ts,tsx}`\n\n```\n$ npm test\n```\n"
  },
  {
    "path": "jest.config.ts",
    "content": "export default {\n  clearMocks: true,\n  collectCoverage: false,\n  coverageDirectory: \"coverage\",\n  moduleFileExtensions: [\"js\", \"jsx\", \"ts\", \"tsx\"],\n  testEnvironment: \"jest-environment-jsdom\",\n  transform: { \"^.+\\\\.(ts|tsx)$\": [\"esbuild-jest\", { sourcemap: true }] },\n  setupFilesAfterEnv: [\"./jest.setup.ts\"],\n  reporters: [\n    \"default\",\n    [\n      \"jest-html-reporters\",\n      {\n        publicPath: \"__reports__\",\n        filename: \"jest.html\",\n      },\n    ],\n  ],\n};\n"
  },
  {
    "path": "jest.setup.ts",
    "content": "import \"@testing-library/jest-dom\";\nimport React from \"react\";\n\nglobal.React = React;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"frontend-testing-book-unittest\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"format\": \"prettier --write \\\"src/**/*.{ts,tsx}\\\"\",\n    \"storybook\": \"start-storybook -p 6006\",\n    \"build-storybook\": \"build-storybook\"\n  },\n  \"dependencies\": {\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.20.12\",\n    \"@babel/preset-typescript\": \"^7.18.6\",\n    \"@storybook/addon-a11y\": \"^6.5.13\",\n    \"@storybook/addon-actions\": \"^6.5.13\",\n    \"@storybook/addon-essentials\": \"^6.5.13\",\n    \"@storybook/addon-interactions\": \"^6.5.13\",\n    \"@storybook/addon-links\": \"^6.5.13\",\n    \"@storybook/builder-webpack5\": \"^6.5.13\",\n    \"@storybook/manager-webpack5\": \"^6.5.13\",\n    \"@storybook/react\": \"^6.5.13\",\n    \"@storybook/testing-library\": \"^0.0.13\",\n    \"@testing-library/jest-dom\": \"^5.16.5\",\n    \"@testing-library/react\": \"^13.4.0\",\n    \"@testing-library/user-event\": \"^14.4.3\",\n    \"@types/jest\": \"^29.4.0\",\n    \"babel-loader\": \"^9.1.2\",\n    \"esbuild\": \"^0.17.5\",\n    \"esbuild-jest\": \"^0.5.0\",\n    \"jest\": \"^29.4.1\",\n    \"jest-environment-jsdom\": \"^29.4.1\",\n    \"jest-html-reporters\": \"^3.1.1\",\n    \"prettier\": \"^2.8.3\",\n    \"prettier-plugin-organize-imports\": \"^3.2.2\",\n    \"ts-jest\": \"^29.0.5\",\n    \"ts-node\": \"^10.9.1\",\n    \"typescript\": \"^4.9.5\",\n    \"whatwg-fetch\": \"^3.6.2\"\n  },\n  \"babel\": {\n    \"presets\": [\n      \"@babel/preset-typescript\"\n    ]\n  }\n}\n"
  },
  {
    "path": "src/03/02/index.test.ts",
    "content": "import { add, sub } from \".\";\n\ndescribe(\"四則演算\", () => {\n  describe(\"add\", () => {\n    test(\"1 + 1 は 2\", () => {\n      expect(add(1, 1)).toBe(2);\n    });\n    test(\"1 + 2 は 3\", () => {\n      expect(add(1, 2)).toBe(3);\n    });\n  });\n  describe(\"sub\", () => {\n    test(\"1 - 1 は 0\", () => {\n      expect(sub(1, 1)).toBe(0);\n    });\n    test(\"2 - 1 は 1\", () => {\n      expect(sub(2, 1)).toBe(1);\n    });\n  });\n});\n"
  },
  {
    "path": "src/03/02/index.ts",
    "content": "export function add(a: number, b: number) {\n  return a + b;\n}\n\nexport function sub(a: number, b: number) {\n  return a - b;\n}\n"
  },
  {
    "path": "src/03/04/index.test.ts",
    "content": "import { add, sub } from \".\";\n\ndescribe(\"四則演算\", () => {\n  describe(\"add\", () => {\n    test(\"返り値は、第一引数と第二引数の「和」である\", () => {\n      expect(add(50, 50)).toBe(100);\n    });\n    test(\"合計の上限は、'100'である\", () => {\n      expect(add(70, 80)).toBe(100);\n    });\n  });\n  describe(\"sub\", () => {\n    test(\"返り値は、第一引数と第二引数の「差」である\", () => {\n      expect(sub(51, 50)).toBe(1);\n    });\n    test(\"返り値の合計は、下限が'0'である\", () => {\n      expect(sub(70, 80)).toBe(0);\n    });\n  });\n});\n"
  },
  {
    "path": "src/03/04/index.ts",
    "content": "export function add(a: number, b: number) {\n  const sum = a + b;\n  if (sum > 100) {\n    return 100;\n  }\n  return sum;\n}\n\nexport function sub(a: number, b: number) {\n  const sum = a - b;\n  if (sum < 0) {\n    return 0;\n  }\n  return sum;\n}\n"
  },
  {
    "path": "src/03/05/index.test.ts",
    "content": "import { add, RangeError, sub } from \".\";\n\ndescribe(\"四則演算\", () => {\n  describe(\"add\", () => {\n    test(\"返り値は、第一引数と第二引数の「和」である\", () => {\n      expect(add(50, 50)).toBe(100);\n    });\n    test(\"合計の上限は、'100'である\", () => {\n      expect(add(70, 80)).toBe(100);\n    });\n    test(\"引数が'0〜100'の範囲外だった場合、例外をスローする\", () => {\n      const message = \"入力値は0〜100の間で入力してください\";\n      expect(() => add(-10, 10)).toThrow(message);\n      expect(() => add(10, -10)).toThrow(message);\n      expect(() => add(-10, 110)).toThrow(message);\n    });\n  });\n  describe(\"sub\", () => {\n    test(\"返り値は、第一引数と第二引数の「差」である\", () => {\n      expect(sub(51, 50)).toBe(1);\n    });\n    test(\"返り値の合計は、下限が'0'である\", () => {\n      expect(sub(70, 80)).toBe(0);\n    });\n    test(\"引数が'0〜100'の範囲外だった場合、例外をスローする\", () => {\n      expect(() => sub(-10, 10)).toThrow(RangeError);\n      expect(() => sub(10, -10)).toThrow(RangeError);\n      expect(() => sub(-10, 110)).toThrow(Error);\n    });\n  });\n});\n"
  },
  {
    "path": "src/03/05/index.ts",
    "content": "export class RangeError extends Error {}\n\nfunction checkRange(value: number) {\n  if (value < 0 || value > 100) {\n    throw new RangeError(\"入力値は0〜100の間で入力してください\");\n  }\n}\n\nexport function add(a: number, b: number) {\n  checkRange(a);\n  checkRange(b);\n  const sum = a + b;\n  if (sum > 100) {\n    return 100;\n  }\n  return sum;\n}\n\nexport function sub(a: number, b: number) {\n  checkRange(a);\n  checkRange(b);\n  const sum = a - b;\n  if (sum < 0) {\n    return 0;\n  }\n  return sum;\n}\n"
  },
  {
    "path": "src/03/06/index.test.ts",
    "content": "describe(\"真偽値の検証\", () => {\n  test(\"「真の値」の検証\", () => {\n    expect(1).toBeTruthy();\n    expect(\"1\").toBeTruthy();\n    expect(true).toBeTruthy();\n    expect(0).not.toBeTruthy();\n    expect(\"\").not.toBeTruthy();\n    expect(false).not.toBeTruthy();\n  });\n  test(\"「偽の値」の検証\", () => {\n    expect(0).toBeFalsy();\n    expect(\"\").toBeFalsy();\n    expect(false).toBeFalsy();\n    expect(1).not.toBeFalsy();\n    expect(\"1\").not.toBeFalsy();\n    expect(true).not.toBeFalsy();\n  });\n  test(\"「null, undefined」の検証\", () => {\n    expect(null).toBeFalsy();\n    expect(undefined).toBeFalsy();\n    expect(null).toBeNull();\n    expect(undefined).toBeUndefined();\n    expect(undefined).not.toBeDefined();\n  });\n});\n\ndescribe(\"数値の検証\", () => {\n  const value = 2 + 2;\n  test(\"検証値 は 期待値 と等しい\", () => {\n    expect(value).toBe(4);\n    expect(value).toEqual(4);\n  });\n  test(\"検証値 は 期待値 より大きい\", () => {\n    expect(value).toBeGreaterThan(3); // 4 > 3\n    expect(value).toBeGreaterThanOrEqual(4); // 4 >= 4\n  });\n  test(\"検証値 は 期待値 より小さい\", () => {\n    expect(value).toBeLessThan(5); // 4 < 5\n    expect(value).toBeLessThanOrEqual(4); // 4 <= 4\n  });\n  test(\"小数計算は正確ではない\", () => {\n    expect(0.1 + 0.2).not.toBe(0.3);\n  });\n  test(\"小数計算の指定桁までを比較する\", () => {\n    expect(0.1 + 0.2).toBeCloseTo(0.3); // デフォルトは 2桁\n    expect(0.1 + 0.2).toBeCloseTo(0.3, 15);\n    expect(0.1 + 0.2).not.toBeCloseTo(0.3, 16);\n  });\n});\n\ndescribe(\"文字列の検証\", () => {\n  const str = \"こんにちは世界\";\n  const obj = { status: 200, message: str };\n  test(\"検証値 は 期待値 と等しい\", () => {\n    expect(str).toBe(\"こんにちは世界\");\n    expect(str).toEqual(\"こんにちは世界\");\n  });\n  test(\"toHaveLength\", () => {\n    expect(str).toHaveLength(7);\n    expect(str).not.toHaveLength(8);\n  });\n  test(\"toContain\", () => {\n    expect(str).toContain(\"世界\");\n    expect(str).not.toContain(\"さようなら\");\n  });\n  test(\"toMatch\", () => {\n    expect(str).toMatch(/世界/);\n    expect(str).not.toMatch(/さようなら/);\n  });\n  test(\"stringContaining\", () => {\n    expect(obj).toEqual({\n      status: 200,\n      message: expect.stringContaining(\"世界\"),\n    });\n  });\n  test(\"stringMatching\", () => {\n    expect(obj).toEqual({\n      status: 200,\n      message: expect.stringMatching(/世界/),\n    });\n  });\n});\n\ndescribe(\"配列の検証\", () => {\n  describe(\"プリミティブ配列\", () => {\n    const tags = [\"Jest\", \"Storybook\", \"Playwright\", \"React\", \"Next.js\"];\n    test(\"toContain\", () => {\n      expect(tags).toContain(\"Jest\");\n      expect(tags).toHaveLength(5);\n    });\n  });\n  describe(\"オブジェクト配列\", () => {\n    const article1 = { author: \"taro\", title: \"Testing Next.js\" };\n    const article2 = { author: \"jiro\", title: \"Storybook play function\" };\n    const article3 = { author: \"hanako\", title: \"Visual Regression Testing \" };\n    const articles = [article1, article2, article3];\n    test(\"toContainEqual\", () => {\n      expect(articles).toContainEqual(article1);\n    });\n    test(\"arrayContaining\", () => {\n      expect(articles).toEqual(expect.arrayContaining([article1, article3]));\n    });\n  });\n});\n\ndescribe(\"オブジェクトの検証\", () => {\n  const author = { name: \"taroyamada\", age: 38 };\n  const article = {\n    title: \"Testing with Jest\",\n    author,\n  };\n  test(\"toMatchObject\", () => {\n    expect(author).toMatchObject({ name: \"taroyamada\", age: 38 });\n    expect(author).toMatchObject({ name: \"taroyamada\" });\n    expect(author).not.toMatchObject({ gender: \"man\" });\n  });\n  test(\"toHaveProperty\", () => {\n    expect(author).toHaveProperty(\"name\");\n    expect(author).toHaveProperty(\"age\");\n  });\n  test(\"objectContaining\", () => {\n    expect(article).toEqual({\n      title: \"Testing with Jest\",\n      author: expect.objectContaining({ name: \"taroyamada\" }),\n    });\n    expect(article).toEqual({\n      title: \"Testing with Jest\",\n      author: expect.not.objectContaining({ gender: \"man\" }),\n    });\n  });\n});\n"
  },
  {
    "path": "src/03/07/index.test.ts",
    "content": "import { timeout, wait } from \".\";\n\ndescribe(\"非同期処理\", () => {\n  describe(\"wait\", () => {\n    test(\"指定時間待つと、経過時間をもって resolve される\", () => {\n      return wait(50).then((duration) => {\n        expect(duration).toBe(50);\n      });\n    });\n    test(\"指定時間待つと、経過時間をもって resolve される\", () => {\n      return expect(wait(50)).resolves.toBe(50);\n    });\n    test(\"指定時間待つと、経過時間をもって resolve される\", async () => {\n      await expect(wait(50)).resolves.toBe(50);\n    });\n    test(\"指定時間待つと、経過時間をもって resolve される\", async () => {\n      expect(await wait(50)).toBe(50);\n    });\n  });\n  describe(\"timeout\", () => {\n    test(\"指定時間待つと、経過時間をもって reject される\", () => {\n      return timeout(50).catch((duration) => {\n        expect(duration).toBe(50);\n      });\n    });\n    test(\"指定時間待つと、経過時間をもって reject される\", () => {\n      return expect(timeout(50)).rejects.toBe(50);\n    });\n    test(\"指定時間待つと、経過時間をもって reject される\", async () => {\n      await expect(timeout(50)).rejects.toBe(50);\n    });\n  });\n});\n\ntest(\"指定時間待つと、経過時間をもって reject される\", async () => {\n  expect.assertions(1);\n  try {\n    await timeout(50); // timeout関数のつもりが、wait関数にしてしまった\n    // ここで終了してしまい、テストは成功する\n  } catch (err) {\n    // アサーションは実行されない\n    expect(err).toBe(50);\n  }\n});\n\ntest(\"return していないため、Promise が解決する前にテストが終了してしまう\", () => {\n  // 失敗を期待して書かれたアサーション\n  expect(wait(2000)).resolves.toBe(3000);\n  // 正しくはアサーションを return する\n  // return expect(wait(2000)).resolves.toBe(3000);\n});\n"
  },
  {
    "path": "src/03/07/index.ts",
    "content": "export function wait(duration: number) {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve(duration);\n    }, duration);\n  });\n}\n\nexport function timeout(duration: number) {\n  return new Promise((_, reject) => {\n    setTimeout(() => {\n      reject(duration);\n    }, duration);\n  });\n}\n"
  },
  {
    "path": "src/04/02/greet.ts",
    "content": "export function greet(name: string) {\n  return `Hello! ${name}.`;\n}\n\nexport function sayGoodBye(name: string) {\n  throw new Error(\"未実装\");\n}\n"
  },
  {
    "path": "src/04/02/greet1.test.ts",
    "content": "import { greet } from \"./greet\";\n\ntest(\"挨拶を返す（本来の実装どおり）\", () => {\n  expect(greet(\"Taro\")).toBe(\"Hello! Taro.\");\n});\n"
  },
  {
    "path": "src/04/02/greet2.test.ts",
    "content": "import { greet } from \"./greet\";\n\njest.mock(\"./greet\");\n\ntest(\"挨拶を返さない（本来の実装ではない）\", () => {\n  expect(greet(\"Taro\")).toBe(undefined);\n});\n"
  },
  {
    "path": "src/04/02/greet3.test.ts",
    "content": "import { greet, sayGoodBye } from \"./greet\";\n\njest.mock(\"./greet\", () => ({\n  sayGoodBye: (name: string) => `Good bye, ${name}.`,\n}));\n\ntest(\"挨拶が未実装（本来の実装ではない）\", () => {\n  expect(greet).toBe(undefined);\n});\n\ntest(\"さよならを返す（本来の実装ではない）\", () => {\n  const message = `${sayGoodBye(\"Taro\")} See you.`;\n  expect(message).toBe(\"Good bye, Taro. See you.\");\n});\n"
  },
  {
    "path": "src/04/02/greet4.test.ts",
    "content": "import { greet, sayGoodBye } from \"./greet\";\n\njest.mock(\"./greet\", () => ({\n  ...jest.requireActual(\"./greet\"),\n  sayGoodBye: (name: string) => `Good bye, ${name}.`,\n}));\n\ntest(\"挨拶を返す（本来の実装どおり）\", () => {\n  expect(greet(\"Taro\")).toBe(\"Hello! Taro.\");\n});\n\ntest(\"さよならを返す（本来の実装ではない）\", () => {\n  const message = `${sayGoodBye(\"Taro\")} See you.`;\n  expect(message).toBe(\"Good bye, Taro. See you.\");\n});\n"
  },
  {
    "path": "src/04/03/index.test.ts",
    "content": "import { getGreet } from \".\";\nimport * as Fetchers from \"../fetchers\";\nimport { httpError } from \"../fetchers/fixtures\";\n\njest.mock(\"../fetchers\");\n\ndescribe(\"getGreet\", () => {\n  test(\"データ取得成功時：ユーザー名がない場合\", async () => {\n    // getMyProfile が resolve した時の値を再現\n    jest.spyOn(Fetchers, \"getMyProfile\").mockResolvedValueOnce({\n      id: \"xxxxxxx-123456\",\n      email: \"taroyamada@myapi.testing.com\",\n    });\n    await expect(getGreet()).resolves.toBe(\"Hello, anonymous user!\");\n  });\n  test(\"データ取得成功時：ユーザー名がある場合\", async () => {\n    jest.spyOn(Fetchers, \"getMyProfile\").mockResolvedValueOnce({\n      id: \"xxxxxxx-123456\",\n      email: \"taroyamada@myapi.testing.com\",\n      name: \"taroyamada\",\n    });\n    await expect(getGreet()).resolves.toBe(\"Hello, taroyamada!\");\n  });\n  test(\"データ取得失敗時\", async () => {\n    // getMyProfile が reject した時の値を再現\n    jest.spyOn(Fetchers, \"getMyProfile\").mockRejectedValueOnce(httpError);\n    await expect(getGreet()).rejects.toMatchObject({\n      err: { message: \"internal server error\" },\n    });\n  });\n  test(\"データ取得失敗時、エラー相当のデータが例外としてスローされる\", async () => {\n    expect.assertions(1);\n    jest.spyOn(Fetchers, \"getMyProfile\").mockRejectedValueOnce(httpError);\n    try {\n      await getGreet();\n    } catch (err) {\n      expect(err).toMatchObject(httpError);\n    }\n  });\n});\n"
  },
  {
    "path": "src/04/03/index.ts",
    "content": "import { getMyProfile } from \"../fetchers\";\n\nexport async function getGreet() {\n  // テストしたいのはここのデータ取得と\n  const data = await getMyProfile();\n  // 取得したデータをここで連結する処理\n  if (!data.name) {\n    return `Hello, anonymous user!`;\n  }\n  return `Hello, ${data.name}!`;\n}\n"
  },
  {
    "path": "src/04/04/index.test.ts",
    "content": "import { getMyArticleLinksByCategory } from \".\";\nimport * as Fetchers from \"../fetchers\";\nimport { getMyArticlesData, httpError } from \"../fetchers/fixtures\";\n\njest.mock(\"../fetchers\");\n\nfunction mockGetMyArticles(status = 200) {\n  if (status > 299) {\n    return jest\n      .spyOn(Fetchers, \"getMyArticles\")\n      .mockRejectedValueOnce(httpError);\n  }\n  return jest\n    .spyOn(Fetchers, \"getMyArticles\")\n    .mockResolvedValueOnce(getMyArticlesData);\n}\n\ntest(\"指定したタグをもつ記事が一件もない場合、null が返る\", async () => {\n  mockGetMyArticles();\n  const data = await getMyArticleLinksByCategory(\"playwright\");\n  expect(data).toBeNull();\n});\n\ntest(\"指定したタグをもつ記事が一件以上ある場合、リンク一覧が返る\", async () => {\n  mockGetMyArticles();\n  const data = await getMyArticleLinksByCategory(\"testing\");\n  expect(data).toMatchObject([\n    {\n      link: \"/articles/howto-testing-with-typescript\",\n      title: \"TypeScript を使ったテストの書き方\",\n    },\n    {\n      link: \"/articles/react-component-testing-with-jest\",\n      title: \"Jest ではじめる React のコンポーネントテスト\",\n    },\n  ]);\n});\n\ntest(\"データ取得に失敗した場合、reject される\", async () => {\n  mockGetMyArticles(500);\n  await getMyArticleLinksByCategory(\"testing\").catch((err) => {\n    expect(err).toMatchObject({\n      err: { message: \"internal server error\" },\n    });\n  });\n});\n"
  },
  {
    "path": "src/04/04/index.ts",
    "content": "import { getMyArticles } from \"../fetchers\";\n\nexport async function getMyArticleLinksByCategory(category: string) {\n  // データを取得する関数\n  const data = await getMyArticles();\n  // 取得したデータのうち、指定したタグが含まれる記事に絞り込む\n  const articles = data.articles.filter((article) =>\n    article.tags.includes(category)\n  );\n  if (!articles.length) {\n    // 該当記事がない場合、null を返す\n    return null;\n  }\n  // 該当記事がある場合、一覧向けに加工したデータを返す\n  return articles.map((article) => ({\n    title: article.title,\n    link: `/articles/${article.id}`,\n  }));\n}\n"
  },
  {
    "path": "src/04/05/checkConfig.test.ts",
    "content": "import { checkConfig } from \"./checkConfig\";\n\ntest(\"モック関数は実行時引数のオブジェクト検証ができる\", () => {\n  const mockFn = jest.fn();\n  checkConfig(mockFn);\n  expect(mockFn).toHaveBeenCalledWith({\n    mock: true,\n    feature: { spy: true },\n  });\n});\n\ntest(\"expect.objectContaining による部分検証\", () => {\n  const mockFn = jest.fn();\n  checkConfig(mockFn);\n  expect(mockFn).toHaveBeenCalledWith(\n    expect.objectContaining({\n      feature: { spy: true },\n    })\n  );\n});\n"
  },
  {
    "path": "src/04/05/checkConfig.ts",
    "content": "const config = {\n  mock: true,\n  feature: { spy: true },\n};\n\nexport function checkConfig(callback?: (payload: object) => void) {\n  callback?.(config);\n}\n"
  },
  {
    "path": "src/04/05/greet.test.ts",
    "content": "import { greet } from \"./greet\";\n\ntest(\"モック関数は実行された\", () => {\n  const mockFn = jest.fn();\n  mockFn();\n  expect(mockFn).toBeCalled();\n});\n\ntest(\"モック関数は実行されていない\", () => {\n  const mockFn = jest.fn();\n  expect(mockFn).not.toBeCalled();\n});\n\ntest(\"モック関数は実行された回数を記録している\", () => {\n  const mockFn = jest.fn();\n  mockFn();\n  expect(mockFn).toHaveBeenCalledTimes(1);\n  mockFn();\n  expect(mockFn).toHaveBeenCalledTimes(2);\n});\n\ntest(\"モック関数は関数の中でも実行できる\", () => {\n  const mockFn = jest.fn();\n  function greet() {\n    mockFn();\n  }\n  greet();\n  expect(mockFn).toHaveBeenCalledTimes(1);\n});\n\ntest(\"モック関数は実行時の引数を記録している\", () => {\n  const mockFn = jest.fn();\n  function greet(message: string) {\n    mockFn(message);\n  }\n  greet(\"hello\");\n  expect(mockFn).toHaveBeenCalledWith(\"hello\");\n});\n\ntest(\"モック関数はテスト対象の引数として使用できる\", () => {\n  const mockFn = jest.fn();\n  greet(\"Jiro\", mockFn);\n  expect(mockFn).toHaveBeenCalledWith(\"Hello! Jiro\");\n});\n"
  },
  {
    "path": "src/04/05/greet.ts",
    "content": "export function greet(name: string, callback?: (message: string) => void) {\n  callback?.(`Hello! ${name}`);\n}\n"
  },
  {
    "path": "src/04/06/index.test.ts",
    "content": "import { checkLength } from \".\";\nimport * as Fetchers from \"../fetchers\";\nimport { postMyArticle } from \"../fetchers\";\nimport { httpError, postMyArticleData } from \"../fetchers/fixtures\";\nimport { ArticleInput } from \"../fetchers/type\";\n\njest.mock(\"../fetchers\");\n\nfunction mockPostMyArticle(input: ArticleInput, status = 200) {\n  if (status > 299) {\n    return jest\n      .spyOn(Fetchers, \"postMyArticle\")\n      .mockRejectedValueOnce(httpError);\n  }\n  try {\n    checkLength(input.title);\n    checkLength(input.body);\n    return jest\n      .spyOn(Fetchers, \"postMyArticle\")\n      .mockResolvedValue({ ...postMyArticleData, ...input });\n  } catch (err) {\n    return jest\n      .spyOn(Fetchers, \"postMyArticle\")\n      .mockRejectedValueOnce(httpError);\n  }\n}\n\nfunction inputFactory(input?: Partial<ArticleInput>) {\n  return {\n    tags: [\"testing\"],\n    title: \"TypeScript を使ったテストの書き方\",\n    body: \"テストを書く時、TypeScript を使うことで、テストの保守性が向上します。\",\n    ...input,\n  };\n}\n\ntest(\"バリデーションに成功した場合、成功レスポンスが返る\", async () => {\n  // バリデーションに通過する入力値を用意\n  const input = inputFactory();\n  // 入力値を含んだ成功レスポンスが返るよう、モックを施す\n  const mock = mockPostMyArticle(input);\n  // テスト対象の関数に、input を与えて実行\n  const data = await postMyArticle(input);\n  // 取得したデータに、入力内容が含まれているかを検証\n  expect(data).toMatchObject(expect.objectContaining(input));\n  // モック関数が呼び出されたかを検証\n  expect(mock).toHaveBeenCalled();\n});\n\ntest(\"バリデーションに失敗した場合、reject される\", async () => {\n  expect.assertions(2);\n  // バリデーションに通過しない入力値を用意\n  const input = inputFactory({ title: \"\", body: \"\" });\n  // 入力値を含んだ成功レスポンスが返るよう、モックを施す\n  const mock = mockPostMyArticle(input);\n  // バリデーションに通過せず reject されるかを検証\n  await postMyArticle(input).catch((err) => {\n    // エラーオブジェクトをもって reject されたことを検証\n    expect(err).toMatchObject({ err: { message: expect.anything() } });\n    // モック関数が呼び出されたことを検証\n    expect(mock).toHaveBeenCalled();\n  });\n});\n\ntest(\"データ取得に失敗した場合、reject される\", async () => {\n  expect.assertions(2);\n  // バリデーションに通過する入力値を用意\n  const input = inputFactory();\n  // 失敗レスポンスが返るようモックを施す\n  const mock = mockPostMyArticle(input, 500);\n  // reject されるかを検証\n  await postMyArticle(input).catch((err) => {\n    // エラーオブジェクトをもって reject されたことを検証\n    expect(err).toMatchObject({ err: { message: expect.anything() } });\n    // モック関数が呼び出されたことを検証\n    expect(mock).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/04/06/index.ts",
    "content": "export class ValidationError extends Error { }\n\nexport function checkLength(value: string) {\n  if (value.length === 0) {\n    throw new ValidationError(\"1文字以上入力してください\");\n  }\n}\n"
  },
  {
    "path": "src/04/07/index.test.ts",
    "content": "import { greetByTime } from \".\";\n\ndescribe(\"greetByTime(\", () => {\n  beforeEach(() => {\n    jest.useFakeTimers();\n  });\n\n  afterEach(() => {\n    jest.useRealTimers();\n  });\n\n  test(\"朝は「おはよう」を返す\", () => {\n    jest.setSystemTime(new Date(2023, 4, 23, 8, 0, 0));\n    expect(greetByTime()).toBe(\"おはよう\");\n  });\n\n  test(\"昼は「こんにちは」を返す\", () => {\n    jest.setSystemTime(new Date(2023, 4, 23, 14, 0, 0));\n    expect(greetByTime()).toBe(\"こんにちは\");\n  });\n\n  test(\"夜は「こんばんは」を返す\", () => {\n    jest.setSystemTime(new Date(2023, 4, 23, 21, 0, 0));\n    expect(greetByTime()).toBe(\"こんばんは\");\n  });\n});\n"
  },
  {
    "path": "src/04/07/index.ts",
    "content": "export function greetByTime() {\n  const hour = new Date().getHours();\n  if (hour < 12) {\n    return \"おはよう\";\n  } else if (hour < 18) {\n    return \"こんにちは\";\n  }\n  return \"こんばんは\";\n}\n"
  },
  {
    "path": "src/04/fetchers/fixtures.ts",
    "content": "import type { Article, Articles, HttpError } from \"./type\";\n\nexport const httpError: HttpError = {\n  err: { message: \"internal server error\" },\n};\n\nexport const getMyArticlesData: Articles = {\n  articles: [\n    {\n      id: \"howto-testing-with-typescript\",\n      createdAt: \"2022-07-19T22:38:41.005Z\",\n      tags: [\"testing\"],\n      title: \"TypeScript を使ったテストの書き方\",\n      body: \"テストを書く時、TypeScript を使うことで、テストの保守性が向上します…\",\n    },\n    {\n      id: \"nextjs-link-component\",\n      createdAt: \"2022-07-19T22:38:41.005Z\",\n      tags: [\"nextjs\"],\n      title: \"Next.js の Link コンポーネント\",\n      body: \"Next.js の画面遷移には、Link コンポーネントを使用します…\",\n    },\n    {\n      id: \"react-component-testing-with-jest\",\n      createdAt: \"2022-07-19T22:38:41.005Z\",\n      tags: [\"testing\", \"react\"],\n      title: \"Jest ではじめる React のコンポーネントテスト\",\n      body: \"Jest は単体テストとして、UIコンポーネントのテストが可能です…\",\n    },\n  ],\n};\n\nexport const postMyArticleData: Article = {\n  id: \"xxxxxxx-123456\",\n  createdAt: \"2022-07-19T22:38:41.005Z\",\n  tags: [\"testing\", \"react\"],\n  title: \"Jest ではじめる React のコンポーネントテスト\",\n  body: \"Jest は単体テストとして、UIコンポーネントのテストが可能です。\",\n};\n"
  },
  {
    "path": "src/04/fetchers/index.ts",
    "content": "import type { Article, ArticleInput, Articles, Profile } from \"./type\";\n\nasync function handleResponse(res: Response) {\n  const data = await res.json();\n  if (!res.ok) {\n    throw data;\n  }\n  return data;\n}\n\nconst host = (path: string) => `https://myapi.testing.com${path}`;\n\nexport function getMyProfile(): Promise<Profile> {\n  return fetch(host(\"/my/profile\")).then(handleResponse);\n}\n\nexport function getMyArticles(): Promise<Articles> {\n  return fetch(host(\"/my/articles\")).then(handleResponse);\n}\n\nexport function postMyArticle(input: ArticleInput): Promise<Article> {\n  return fetch(host(\"/my/articles\"), {\n    method: \"POST\",\n    body: JSON.stringify(input),\n  }).then(handleResponse);\n}\n"
  },
  {
    "path": "src/04/fetchers/type.ts",
    "content": "export type HttpError = {\n  err: { message: string };\n};\n\nexport type Profile = {\n  id: string;\n  name?: string;\n  age?: number;\n  email: string;\n};\n\nexport type Article = {\n  id: string;\n  createdAt: string;\n  tags: string[];\n  title: string;\n  body: string;\n};\n\nexport type Articles = {\n  articles: Article[];\n};\n\nexport type ArticleInput = {\n  tags: string[];\n  title: string;\n  body: string;\n};\n\nexport type PartialArticleInput = {\n  tags?: string[];\n  title?: string;\n  body?: string;\n};\n"
  },
  {
    "path": "src/05/03/Form.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { Form } from \"./Form\";\n\nexport default {\n  component: Form,\n  args: { name: \"taro\" },\n} as ComponentMeta<typeof Form>;\n\ntype Story = ComponentStoryObj<typeof Form>;\n\nexport const Default: Story = {};\n"
  },
  {
    "path": "src/05/03/Form.test.tsx",
    "content": "import { fireEvent, logRoles, render, screen } from \"@testing-library/react\";\nimport { Form } from \"./Form\";\n\ntest(\"名前の表示\", () => {\n  render(<Form name=\"taro\" />);\n  expect(screen.getByText(\"taro\")).toBeInTheDocument();\n});\n\ntest(\"ボタンの表示\", () => {\n  render(<Form name=\"taro\" />);\n  expect(screen.getByRole(\"button\")).toBeInTheDocument();\n});\n\ntest(\"見出しの表示\", () => {\n  render(<Form name=\"taro\" />);\n  expect(screen.getByRole(\"heading\")).toHaveTextContent(\"アカウント情報\");\n});\n\ntest(\"ボタンを押下すると、イベントハンドラーが呼ばれる\", () => {\n  const mockFn = jest.fn();\n  render(<Form name=\"taro\" onSubmit={mockFn} />);\n  fireEvent.click(screen.getByRole(\"button\"));\n  expect(mockFn).toHaveBeenCalled();\n});\n\ntest(\"Snapshot: アカウント名「taro」が表示される\", () => {\n  const { container } = render(<Form name=\"taro\" />);\n  expect(container).toMatchSnapshot();\n});\n\ntest(\"logRoles: レンダリング結果からロール・アクセシブルネームを確認\", () => {\n  const { container } = render(<Form name=\"taro\" />);\n  logRoles(container);\n});\n"
  },
  {
    "path": "src/05/03/Form.tsx",
    "content": "type Props = {\n  name: string;\n  onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;\n};\nexport const Form = ({ name, onSubmit }: Props) => {\n  return (\n    <form\n      onSubmit={(event) => {\n        event.preventDefault();\n        onSubmit?.(event);\n      }}\n    >\n      <h2>アカウント情報</h2>\n      <p>{name}</p>\n      <div>\n        <button>編集する</button>\n      </div>\n    </form>\n  );\n};\n"
  },
  {
    "path": "src/05/03/__snapshots__/Form.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Snapshot: アカウント名「taro」が表示される 1`] = `\n<div>\n  <form>\n    <h2>\n      アカウント情報\n    </h2>\n    <p>\n      taro\n    </p>\n    <div>\n      <button>\n        編集する\n      </button>\n    </div>\n  </form>\n</div>\n`;\n"
  },
  {
    "path": "src/05/04/ArticleList.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { ArticleList } from \"./ArticleList\";\nimport { items } from \"./fixture\";\n\nexport default {\n  component: ArticleList,\n} as ComponentMeta<typeof ArticleList>;\n\ntype Story = ComponentStoryObj<typeof ArticleList>;\n\nexport const Default: Story = {\n  args: { items },\n};\n\nexport const NoItem: Story = {\n  args: { items: [] },\n};\n"
  },
  {
    "path": "src/05/04/ArticleList.test.tsx",
    "content": "import { render, screen, within } from \"@testing-library/react\";\nimport { ArticleList } from \"./ArticleList\";\nimport { items } from \"./fixture\";\n\ntest(\"タイトルの表示\", () => {\n  render(<ArticleList items={items} />);\n  expect(screen.getByRole(\"heading\", { name: \"記事一覧\" })).toBeInTheDocument();\n});\n\ntest(\"items の数だけ一覧表示される\", () => {\n  render(<ArticleList items={items} />);\n  expect(screen.getAllByRole(\"listitem\")).toHaveLength(3);\n});\n\ntest(\"items の数だけ一覧表示される\", () => {\n  render(<ArticleList items={items} />);\n  const list = screen.getByRole(\"list\");\n  expect(list).toBeInTheDocument();\n  expect(within(list).getAllByRole(\"listitem\")).toHaveLength(3);\n});\n\ntest(\"一覧アイテムが空のとき「投稿記事がありません」が表示される\", () => {\n  render(<ArticleList items={[]} />);\n  const list = screen.queryByRole(\"list\");\n  expect(list).not.toBeInTheDocument();\n  expect(list).toBeNull();\n  expect(screen.getByText(\"投稿記事がありません\")).toBeInTheDocument();\n});\n\ntest(\"Snapshot: items の数だけ一覧表示される\", () => {\n  const { container } = render(<ArticleList items={items} />);\n  expect(container).toMatchSnapshot();\n});\n"
  },
  {
    "path": "src/05/04/ArticleList.tsx",
    "content": "import { ArticleListItem, ItemProps } from \"./ArticleListItem\";\n\ntype Props = {\n  items: ItemProps[];\n};\n\nexport const ArticleList = ({ items }: Props) => {\n  return (\n    <div>\n      <h2>記事一覧</h2>\n      {items.length ? (\n        <ul>\n          {items.map((item) => (\n            <ArticleListItem {...item} key={item.id} />\n          ))}\n        </ul>\n      ) : (\n        <p>投稿記事がありません</p>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/05/04/ArticleListItem.test.tsx",
    "content": "import { render, screen } from \"@testing-library/react\";\nimport { ArticleListItem, ItemProps } from \"./ArticleListItem\";\n\nconst item: ItemProps = {\n  id: \"howto-testing-with-typescript\",\n  title: \"TypeScript を使ったテストの書き方\",\n  body: \"テストを書く時、TypeScript を使うことで、テストの保守性が向上します…\",\n};\n\ntest(\"ID に紐づいたリンクが表示される\", () => {\n  render(<ArticleListItem {...item} />);\n  expect(screen.getByRole(\"link\", { name: \"もっと見る\" })).toHaveAttribute(\n    \"href\",\n    \"/articles/howto-testing-with-typescript\"\n  );\n});\n\ntest(\"Snapshot: 一覧要素が表示される\", () => {\n  const { container } = render(<ArticleListItem {...item} />);\n  expect(container).toMatchSnapshot();\n});\n"
  },
  {
    "path": "src/05/04/ArticleListItem.tsx",
    "content": "export type ItemProps = {\n  id: string;\n  title: string;\n  body: string;\n};\n\nexport const ArticleListItem = ({ id, title, body }: ItemProps) => {\n  return (\n    <li>\n      <h3>{title}</h3>\n      <p>{body}</p>\n      <a href={`/articles/${id}`}>もっと見る</a>\n    </li>\n  );\n};\n"
  },
  {
    "path": "src/05/04/__snapshots__/ArticleList.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Snapshot: items の数だけ一覧表示される 1`] = `\n<div>\n  <div>\n    <h2>\n      記事一覧\n    </h2>\n    <ul>\n      <li>\n        <h3>\n          TypeScript を使ったテストの書き方\n        </h3>\n        <p>\n          テストを書く時、TypeScript を使うことで、テストの保守性が向上します…\n        </p>\n        <a\n          href=\"/articles/howto-testing-with-typescript\"\n        >\n          もっと見る\n        </a>\n      </li>\n      <li>\n        <h3>\n          Next.js の Link コンポーネント\n        </h3>\n        <p>\n          Next.js の画面遷移には、Link コンポーネントを使用します…\n        </p>\n        <a\n          href=\"/articles/nextjs-link-component\"\n        >\n          もっと見る\n        </a>\n      </li>\n      <li>\n        <h3>\n          Jest ではじめる React のコンポーネントテスト\n        </h3>\n        <p>\n          Jest は単体テストとして、UIコンポーネントのテストが可能です…\n        </p>\n        <a\n          href=\"/articles/react-component-testing-with-jest\"\n        >\n          もっと見る\n        </a>\n      </li>\n    </ul>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "src/05/04/__snapshots__/ArticleListItem.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Snapshot: 一覧要素が表示される 1`] = `\n<div>\n  <li>\n    <h3>\n      TypeScript を使ったテストの書き方\n    </h3>\n    <p>\n      テストを書く時、TypeScript を使うことで、テストの保守性が向上します…\n    </p>\n    <a\n      href=\"/articles/howto-testing-with-typescript\"\n    >\n      もっと見る\n    </a>\n  </li>\n</div>\n`;\n"
  },
  {
    "path": "src/05/04/fixture.ts",
    "content": "import { ItemProps } from \"./ArticleListItem\";\n\nexport const items: ItemProps[] = [\n  {\n    id: \"howto-testing-with-typescript\",\n    title: \"TypeScript を使ったテストの書き方\",\n    body: \"テストを書く時、TypeScript を使うことで、テストの保守性が向上します…\",\n  },\n  {\n    id: \"nextjs-link-component\",\n    title: \"Next.js の Link コンポーネント\",\n    body: \"Next.js の画面遷移には、Link コンポーネントを使用します…\",\n  },\n  {\n    id: \"react-component-testing-with-jest\",\n    title: \"Jest ではじめる React のコンポーネントテスト\",\n    body: \"Jest は単体テストとして、UIコンポーネントのテストが可能です…\",\n  },\n];\n"
  },
  {
    "path": "src/05/05/Agreement.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { Agreement } from \"./Agreement\";\n\nexport default {\n  component: Agreement,\n} as ComponentMeta<typeof Agreement>;\n\ntype Story = ComponentStoryObj<typeof Agreement>;\n\nexport const Default: Story = {};\n"
  },
  {
    "path": "src/05/05/Agreement.test.tsx",
    "content": "import { render, screen } from \"@testing-library/react\";\nimport { Agreement } from \"./Agreement\";\n\ntest(\"fieldset のアクセシブルネームは、legend を引用している\", () => {\n  render(<Agreement />);\n  expect(\n    screen.getByRole(\"group\", { name: \"利用規約の同意\" })\n  ).toBeInTheDocument();\n});\n\ntest(\"チェックボックスはチェックが入っていない\", () => {\n  render(<Agreement />);\n  expect(screen.getByRole(\"checkbox\")).not.toBeChecked();\n});\n\ntest(\"利用規約へのリンクがある\", () => {\n  render(<Agreement />);\n  expect(screen.getByRole(\"link\")).toBeInTheDocument();\n  expect(screen.getByRole(\"link\")).toHaveTextContent(\"利用規約\");\n  expect(screen.getByRole(\"link\")).toHaveAttribute(\"href\", \"/terms\");\n  expect(screen.getByRole(\"link\", { name: \"利用規約\" })).toHaveAttribute(\n    \"href\",\n    \"/terms\"\n  );\n});\n\ntest(\"Snapshot: 利用規約の同意が表示される\", () => {\n  const { container } = render(<Agreement />);\n  expect(container).toMatchSnapshot();\n});\n"
  },
  {
    "path": "src/05/05/Agreement.tsx",
    "content": "type Props = {\n  onChange?: React.ChangeEventHandler<HTMLInputElement>;\n};\n\nexport const Agreement = ({ onChange }: Props) => {\n  return (\n    <fieldset>\n      <legend>利用規約の同意</legend>\n      <label>\n        <input type=\"checkbox\" onChange={onChange} />\n        当サービスの<a href=\"/terms\">利用規約</a>を確認し、これに同意します\n      </label>\n    </fieldset>\n  );\n};\n"
  },
  {
    "path": "src/05/05/Form.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { Form } from \"./Form\";\n\nexport default {\n  component: Form,\n} as ComponentMeta<typeof Form>;\n\ntype Story = ComponentStoryObj<typeof Form>;\n\nexport const Default: Story = {};\n"
  },
  {
    "path": "src/05/05/Form.test.tsx",
    "content": "import { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Form } from \"./Form\";\n\nconst user = userEvent.setup();\n\ntest(\"form のアクセシブルネームは、見出しを引用している\", () => {\n  render(<Form />);\n  expect(\n    screen.getByRole(\"form\", { name: \"新規アカウント登録\" })\n  ).toBeInTheDocument();\n});\n\ntest(\"主要エリアが表示されている\", () => {\n  render(<Form />);\n  expect(\n    screen.getByRole(\"heading\", { name: \"新規アカウント登録\" })\n  ).toBeInTheDocument();\n  expect(\n    screen.getByRole(\"group\", { name: \"アカウント情報の入力\" })\n  ).toBeInTheDocument();\n  expect(\n    screen.getByRole(\"group\", { name: \"利用規約の同意\" })\n  ).toBeInTheDocument();\n  expect(\n    screen.getByRole(\"button\", { name: \"サインアップ\" })\n  ).toBeInTheDocument();\n});\n\ntest(\"「サインアップ」ボタンは非活性\", () => {\n  render(<Form />);\n  expect(screen.getByRole(\"button\", { name: \"サインアップ\" })).toBeDisabled();\n});\n\ntest(\"「利用規約の同意」チェックボックスを押下すると「サインアップ」ボタンは活性化\", async () => {\n  render(<Form />);\n  await user.click(screen.getByRole(\"checkbox\"));\n  expect(screen.getByRole(\"button\", { name: \"サインアップ\" })).toBeEnabled();\n});\n\ntest(\"Snapshot: 新規アカウント登録フォームが表示される\", () => {\n  const { container } = render(<Form />);\n  expect(container).toMatchSnapshot();\n});\n"
  },
  {
    "path": "src/05/05/Form.tsx",
    "content": "import { useId, useState } from \"react\";\nimport { Agreement } from \"./Agreement\";\nimport { InputAccount } from \"./InputAccount\";\n\nexport const Form = () => {\n  const [checked, setChecked] = useState(false);\n  const headingId = useId();\n  return (\n    <form aria-labelledby={headingId}>\n      <h2 id={headingId}>新規アカウント登録</h2>\n      <InputAccount />\n      <Agreement\n        onChange={(event) => {\n          setChecked(event.currentTarget.checked);\n        }}\n      />\n      <div>\n        <button disabled={!checked}>サインアップ</button>\n      </div>\n    </form>\n  );\n};\n"
  },
  {
    "path": "src/05/05/InputAccount.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { InputAccount } from \"./InputAccount\";\n\nexport default {\n  component: InputAccount,\n} as ComponentMeta<typeof InputAccount>;\n\ntype Story = ComponentStoryObj<typeof InputAccount>;\n\nexport const Default: Story = {};\n"
  },
  {
    "path": "src/05/05/InputAccount.test.tsx",
    "content": "import { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { InputAccount } from \"./InputAccount\";\n\nconst user = userEvent.setup();\n\ntest(\"fieldset のアクセシブルネームは、legend を引用している\", () => {\n  render(<InputAccount />);\n  expect(\n    screen.getByRole(\"group\", { name: \"アカウント情報の入力\" })\n  ).toBeInTheDocument();\n});\n\ntest(\"メールアドレス入力欄\", async () => {\n  render(<InputAccount />);\n  const textbox = screen.getByRole(\"textbox\", { name: \"メールアドレス\" });\n  const value = \"taro.tanaka@example.com\";\n  await user.type(textbox, value);\n  expect(screen.getByDisplayValue(value)).toBeInTheDocument();\n});\n\ntest(\"パスワード入力欄\", async () => {\n  render(<InputAccount />);\n  expect(() => screen.getByPlaceholderText(\"8文字以上で入力\")).not.toThrow();\n  expect(() => screen.getByRole(\"textbox\", { name: \"パスワード\" })).toThrow();\n});\n\ntest(\"パスワード入力欄\", async () => {\n  render(<InputAccount />);\n  const password = screen.getByPlaceholderText(\"8文字以上で入力\");\n  const value = \"abcd1234\";\n  await user.type(password, value);\n  expect(screen.getByDisplayValue(value)).toBeInTheDocument();\n});\n\ntest(\"Snapshot: アカウント情報の入力フォームが表示される\", () => {\n  const { container } = render(<InputAccount />);\n  expect(container).toMatchSnapshot();\n});\n"
  },
  {
    "path": "src/05/05/InputAccount.tsx",
    "content": "export const InputAccount = () => {\n  return (\n    <fieldset>\n      <legend>アカウント情報の入力</legend>\n      <div>\n        <label>\n          メールアドレス\n          <input type=\"text\" placeholder=\"example@test.com\" />\n        </label>\n      </div>\n      <div>\n        <label>\n          パスワード\n          <input type=\"password\" placeholder=\"8文字以上で入力\" />\n        </label>\n      </div>\n    </fieldset>\n  );\n};\n"
  },
  {
    "path": "src/05/05/__snapshots__/Agreement.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Snapshot: 利用規約の同意が表示される 1`] = `\n<div>\n  <fieldset>\n    <legend>\n      利用規約の同意\n    </legend>\n    <label>\n      <input\n        type=\"checkbox\"\n      />\n      当サービスの\n      <a\n        href=\"/terms\"\n      >\n        利用規約\n      </a>\n      を確認し、これに同意します\n    </label>\n  </fieldset>\n</div>\n`;\n"
  },
  {
    "path": "src/05/05/__snapshots__/Form.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Snapshot: 新規アカウント登録フォームが表示される 1`] = `\n<div>\n  <form\n    aria-labelledby=\":r4:\"\n  >\n    <h2\n      id=\":r4:\"\n    >\n      新規アカウント登録\n    </h2>\n    <fieldset>\n      <legend>\n        アカウント情報の入力\n      </legend>\n      <div>\n        <label>\n          メールアドレス\n          <input\n            placeholder=\"example@test.com\"\n            type=\"text\"\n          />\n        </label>\n      </div>\n      <div>\n        <label>\n          パスワード\n          <input\n            placeholder=\"8文字以上で入力\"\n            type=\"password\"\n          />\n        </label>\n      </div>\n    </fieldset>\n    <fieldset>\n      <legend>\n        利用規約の同意\n      </legend>\n      <label>\n        <input\n          type=\"checkbox\"\n        />\n        当サービスの\n        <a\n          href=\"/terms\"\n        >\n          利用規約\n        </a>\n        を確認し、これに同意します\n      </label>\n    </fieldset>\n    <div>\n      <button\n        disabled=\"\"\n      >\n        サインアップ\n      </button>\n    </div>\n  </form>\n</div>\n`;\n"
  },
  {
    "path": "src/05/05/__snapshots__/InputAccount.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Snapshot: アカウント情報の入力フォームが表示される 1`] = `\n<div>\n  <fieldset>\n    <legend>\n      アカウント情報の入力\n    </legend>\n    <div>\n      <label>\n        メールアドレス\n        <input\n          placeholder=\"example@test.com\"\n          type=\"text\"\n        />\n      </label>\n    </div>\n    <div>\n      <label>\n        パスワード\n        <input\n          placeholder=\"8文字以上で入力\"\n          type=\"password\"\n        />\n      </label>\n    </div>\n  </fieldset>\n</div>\n`;\n"
  },
  {
    "path": "src/05/06/ContactNumber.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { ContactNumber } from \"./ContactNumber\";\n\nexport default {\n  component: ContactNumber,\n} as ComponentMeta<typeof ContactNumber>;\n\ntype Story = ComponentStoryObj<typeof ContactNumber>;\n\nexport const Default: Story = {};\n"
  },
  {
    "path": "src/05/06/ContactNumber.test.tsx",
    "content": "import { render, screen } from \"@testing-library/react\";\nimport { ContactNumber } from \"./ContactNumber\";\n\ndescribe(\"連絡先\", () => {\n  test(\"タイトル\", () => {\n    render(<ContactNumber />);\n    expect(screen.getByText(\"連絡先\")).toBeInTheDocument();\n  });\n  test(\"電話番号\", () => {\n    render(<ContactNumber />);\n    expect(screen.getByLabelText(\"電話番号\")).toBeInTheDocument();\n  });\n  test(\"お名前\", () => {\n    render(<ContactNumber />);\n    expect(screen.getByLabelText(\"お名前\")).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/05/06/ContactNumber.tsx",
    "content": "export const ContactNumber = () => {\n  return (\n    <fieldset>\n      <legend>連絡先</legend>\n      <div>\n        <label>\n          電話番号\n          <input type=\"text\" name=\"phoneNumber\" />\n        </label>\n      </div>\n      <div>\n        <label>\n          お名前\n          <input type=\"text\" name=\"name\" />\n        </label>\n      </div>\n    </fieldset>\n  );\n};\n"
  },
  {
    "path": "src/05/06/DeliveryAddress.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { DeliveryAddress } from \"./DeliveryAddress\";\n\nexport default {\n  component: DeliveryAddress,\n} as ComponentMeta<typeof DeliveryAddress>;\n\ntype Story = ComponentStoryObj<typeof DeliveryAddress>;\n\nexport const Default: Story = {};\n"
  },
  {
    "path": "src/05/06/DeliveryAddress.test.tsx",
    "content": "import { render, screen } from \"@testing-library/react\";\nimport { DeliveryAddress } from \"./DeliveryAddress\";\n\ndescribe(\"お届け先\", () => {\n  test(\"タイトル\", () => {\n    render(<DeliveryAddress />);\n    expect(screen.getByText(\"お届け先\")).toBeInTheDocument();\n  });\n  test(\"タイトルが変更できる\", () => {\n    render(<DeliveryAddress title=\"新しいお届け先\" />);\n    expect(screen.getByText(\"新しいお届け先\")).toBeInTheDocument();\n  });\n  test(\"郵便番号\", () => {\n    render(<DeliveryAddress />);\n    expect(\n      screen.getByRole(\"textbox\", { name: \"郵便番号\" })\n    ).toBeInTheDocument();\n  });\n  test(\"都道府県\", () => {\n    render(<DeliveryAddress />);\n    expect(\n      screen.getByRole(\"textbox\", { name: \"都道府県\" })\n    ).toBeInTheDocument();\n  });\n  test(\"市区町村\", () => {\n    render(<DeliveryAddress />);\n    expect(\n      screen.getByRole(\"textbox\", { name: \"市区町村\" })\n    ).toBeInTheDocument();\n  });\n  test(\"番地番号\", () => {\n    render(<DeliveryAddress />);\n    expect(\n      screen.getByRole(\"textbox\", { name: \"番地番号\" })\n    ).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/05/06/DeliveryAddress.tsx",
    "content": "export const DeliveryAddress = ({ title = \"お届け先\" }: { title?: string }) => {\n  return (\n    <fieldset>\n      <legend>{title}</legend>\n      <div>\n        <label>\n          郵便番号\n          <input type=\"text\" name=\"postalCode\" placeholder=\"167-0051\" />\n        </label>\n      </div>\n      <div>\n        <label>\n          都道府県\n          <input type=\"text\" name=\"prefectures\" placeholder=\"東京都\" />\n        </label>\n      </div>\n      <div>\n        <label>\n          市区町村\n          <input type=\"text\" name=\"municipalities\" placeholder=\"杉並区荻窪1\" />\n        </label>\n      </div>\n      <div>\n        <label>\n          番地番号\n          <input type=\"text\" name=\"streetNumber\" placeholder=\"00-00\" />\n        </label>\n      </div>\n    </fieldset>\n  );\n};\n"
  },
  {
    "path": "src/05/06/Form.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { deliveryAddresses } from \"./fixtures\";\nimport { Form } from \"./Form\";\n\nexport default {\n  component: Form,\n} as ComponentMeta<typeof Form>;\n\ntype Story = ComponentStoryObj<typeof Form>;\n\nexport const NoDeliveryAddresses: Story = {\n  storyName: \"過去のお届け先がない場合\",\n  args: { deliveryAddresses: [] },\n};\n\nexport const HasDeliveryAddresses: Story = {\n  storyName: \"過去のお届け先がある場合\",\n  args: { deliveryAddresses },\n};\n"
  },
  {
    "path": "src/05/06/Form.test.tsx",
    "content": "import { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { deliveryAddresses } from \"./fixtures\";\nimport { Form } from \"./Form\";\n\nconst user = userEvent.setup();\n\nasync function inputContactNumber(\n  inputValues = {\n    name: \"田中 太郎\",\n    phoneNumber: \"000-0000-0000\",\n  }\n) {\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"電話番号\" }),\n    inputValues.phoneNumber\n  );\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"お名前\" }),\n    inputValues.name\n  );\n  return inputValues;\n}\n\nasync function inputDeliveryAddress(\n  inputValues = {\n    postalCode: \"167-0051\",\n    prefectures: \"東京都\",\n    municipalities: \"杉並区荻窪1\",\n    streetNumber: \"00-00\",\n  }\n) {\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"郵便番号\" }),\n    inputValues.postalCode\n  );\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"都道府県\" }),\n    inputValues.prefectures\n  );\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"市区町村\" }),\n    inputValues.municipalities\n  );\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"番地番号\" }),\n    inputValues.streetNumber\n  );\n  return inputValues;\n}\n\nasync function clickSubmit() {\n  await user.click(\n    screen.getByRole(\"button\", { name: \"注文内容の確認へ進む\" })\n  );\n}\n\nfunction mockHandleSubmit() {\n  const mockFn = jest.fn();\n  const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n    const formData = new FormData(event.currentTarget);\n    const data: { [k: string]: unknown } = {};\n    formData.forEach((value, key) => (data[key] = value));\n    mockFn(data);\n  };\n  return [mockFn, onSubmit] as const;\n}\n\ndescribe(\"過去のお届け先がない場合\", () => {\n  test(\"お届け先入力欄がある\", () => {\n    render(<Form />);\n    expect(screen.getByRole(\"group\", { name: \"連絡先\" })).toBeInTheDocument();\n    expect(screen.getByRole(\"group\", { name: \"お届け先\" })).toBeInTheDocument();\n  });\n\n  test(\"入力・送信すると、入力内容が送信される\", async () => {\n    const [mockFn, onSubmit] = mockHandleSubmit();\n    render(<Form onSubmit={onSubmit} />);\n    const contactNumber = await inputContactNumber();\n    const deliveryAddress = await inputDeliveryAddress();\n    await clickSubmit();\n    expect(mockFn).toHaveBeenCalledWith(\n      expect.objectContaining({ ...contactNumber, ...deliveryAddress })\n    );\n  });\n\n  test(\"Snapshot\", () => {\n    const { container } = render(<Form />);\n    expect(container).toMatchSnapshot();\n  });\n});\n\ndescribe(\"過去のお届け先がある場合\", () => {\n  test(\"設問に答えるまで、お届け先を選べない\", () => {\n    render(<Form deliveryAddresses={deliveryAddresses} />);\n    expect(\n      screen.getByRole(\"group\", { name: \"新しいお届け先を登録しますか？\" })\n    ).toBeInTheDocument();\n    expect(\n      screen.getByRole(\"group\", { name: \"過去のお届け先\" })\n    ).toBeDisabled();\n  });\n\n  test(\"「いいえ」を選択・入力・送信すると、入力内容が送信される\", async () => {\n    const [mockFn, onSubmit] = mockHandleSubmit();\n    render(<Form deliveryAddresses={deliveryAddresses} onSubmit={onSubmit} />);\n    await user.click(screen.getByLabelText(\"いいえ\"));\n    expect(\n      screen.getByRole(\"group\", { name: \"過去のお届け先\" })\n    ).toBeInTheDocument();\n    const inputValues = await inputContactNumber();\n    await clickSubmit();\n    expect(mockFn).toHaveBeenCalledWith(expect.objectContaining(inputValues));\n  });\n\n  test(\"「はい」を選択・入力・送信すると、入力内容が送信される\", async () => {\n    const [mockFn, onSubmit] = mockHandleSubmit();\n    render(<Form deliveryAddresses={deliveryAddresses} onSubmit={onSubmit} />);\n    await user.click(screen.getByLabelText(\"はい\"));\n    expect(\n      screen.getByRole(\"group\", { name: \"新しいお届け先\" })\n    ).toBeInTheDocument();\n    const contactNumber = await inputContactNumber();\n    const deliveryAddress = await inputDeliveryAddress();\n    await clickSubmit();\n    expect(mockFn).toHaveBeenCalledWith(\n      expect.objectContaining({ ...contactNumber, ...deliveryAddress })\n    );\n  });\n\n  test(\"Snapshot\", () => {\n    const { container } = render(\n      <Form deliveryAddresses={deliveryAddresses} />\n    );\n    expect(container).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "src/05/06/Form.tsx",
    "content": "import { useState } from \"react\";\nimport { ContactNumber } from \"./ContactNumber\";\nimport { DeliveryAddress } from \"./DeliveryAddress\";\nimport { PastDeliveryAddress } from \"./PastDeliveryAddress\";\nimport { RegisterDeliveryAddress } from \"./RegisterDeliveryAddress\";\n\nexport type AddressOption = React.ComponentProps<\"option\"> & { id: string };\nexport type Props = {\n  deliveryAddresses?: AddressOption[];\n  onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;\n};\nexport const Form = (props: Props) => {\n  const [registerNew, setRegisterNew] = useState<boolean | undefined>(\n    undefined\n  );\n  return (\n    <form onSubmit={props.onSubmit}>\n      <h2>お届け先情報の入力</h2>\n      <ContactNumber />\n      {props.deliveryAddresses?.length ? (\n        <>\n          <RegisterDeliveryAddress onChange={setRegisterNew} />\n          {registerNew ? (\n            <DeliveryAddress title=\"新しいお届け先\" />\n          ) : (\n            <PastDeliveryAddress\n              disabled={registerNew === undefined}\n              options={props.deliveryAddresses}\n            />\n          )}\n        </>\n      ) : (\n        <DeliveryAddress />\n      )}\n      <hr />\n      <div>\n        <button>注文内容の確認へ進む</button>\n      </div>\n    </form>\n  );\n};\n"
  },
  {
    "path": "src/05/06/PastDeliveryAddress.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { PastDeliveryAddress } from \"./PastDeliveryAddress\";\n\nexport default {\n  component: PastDeliveryAddress,\n  args: {\n    options: [\n      {\n        id: \"xxx\",\n        value: \"xxx\",\n        children: \"〒167-0051 東京都杉並区荻窪1-00-00\",\n      },\n    ],\n  },\n} as ComponentMeta<typeof PastDeliveryAddress>;\n\ntype Story = ComponentStoryObj<typeof PastDeliveryAddress>;\n\nexport const Disabled: Story = {\n  args: {\n    disabled: true,\n  },\n};\n\nexport const Enabled: Story = {\n  args: {\n    disabled: false,\n  },\n};\n"
  },
  {
    "path": "src/05/06/PastDeliveryAddress.test.tsx",
    "content": "import { render, screen } from \"@testing-library/react\";\nimport { PastDeliveryAddress } from \"./PastDeliveryAddress\";\n\ndescribe(\"過去のお届け先\", () => {\n  const options = [\n    {\n      id: \"xxx\",\n      value: \"xxx\",\n      children: \"〒167-0051 東京都杉並区荻窪1-00-00\",\n    },\n  ];\n  test(\"disabled={true} の場合、combobox も非活性\", () => {\n    render(<PastDeliveryAddress disabled={true} options={options} />);\n    expect(screen.getByRole(\"combobox\")).toBeDisabled();\n  });\n});\n"
  },
  {
    "path": "src/05/06/PastDeliveryAddress.tsx",
    "content": "import { AddressOption } from \"./Form\";\n\nexport const PastDeliveryAddress = ({\n  disabled,\n  options,\n}: {\n  disabled?: boolean;\n  options: AddressOption[];\n}) => {\n  return (\n    <fieldset disabled={disabled} style={{ opacity: disabled ? 0.3 : 1 }}>\n      <legend>過去のお届け先</legend>\n      <select name=\"pastDeliveryAddress\">\n        {options.map(({ id, ...opt }) => (\n          <option key={id} {...opt} />\n        ))}\n      </select>\n    </fieldset>\n  );\n};\n"
  },
  {
    "path": "src/05/06/RegisterDeliveryAddress.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { RegisterDeliveryAddress } from \"./RegisterDeliveryAddress\";\n\nexport default {\n  component: RegisterDeliveryAddress,\n} as ComponentMeta<typeof RegisterDeliveryAddress>;\n\ntype Story = ComponentStoryObj<typeof RegisterDeliveryAddress>;\n\nexport const Default: Story = {};\n"
  },
  {
    "path": "src/05/06/RegisterDeliveryAddress.test.tsx",
    "content": "import { fireEvent, render, screen } from \"@testing-library/react\";\nimport { RegisterDeliveryAddress } from \"./RegisterDeliveryAddress\";\n\ndescribe(\"新しいお届け先を登録しますか？\", () => {\n  test(\"ラジオボタンをクリックすると、コールバックハンドラが呼ばれる\", () => {\n    const fn = jest.fn();\n    render(<RegisterDeliveryAddress onChange={fn} />);\n    fireEvent.click(screen.getByLabelText(\"いいえ\"));\n    expect(fn).toHaveBeenCalledWith(false);\n    fireEvent.click(screen.getByLabelText(\"はい\"));\n    expect(fn).toHaveBeenCalledWith(true);\n  });\n});\n"
  },
  {
    "path": "src/05/06/RegisterDeliveryAddress.tsx",
    "content": "type Props = { onChange: (flag: boolean) => void };\n\nexport const RegisterDeliveryAddress = ({ onChange }: Props) => {\n  return (\n    <fieldset>\n      <legend>新しいお届け先を登録しますか？</legend>\n      <label>\n        <input\n          type=\"radio\"\n          name=\"registerDeliveryAddress\"\n          value={0}\n          onChange={() => {\n            onChange(false);\n          }}\n        />\n        いいえ\n      </label>\n      <label>\n        <input\n          type=\"radio\"\n          name=\"registerDeliveryAddress\"\n          value={1}\n          onChange={() => {\n            onChange(true);\n          }}\n        />\n        はい\n      </label>\n    </fieldset>\n  );\n};\n"
  },
  {
    "path": "src/05/06/__snapshots__/Form.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`過去のお届け先がある場合 Snapshot 1`] = `\n<div>\n  <form>\n    <h2>\n      お届け先情報の入力\n    </h2>\n    <fieldset>\n      <legend>\n        連絡先\n      </legend>\n      <div>\n        <label>\n          電話番号\n          <input\n            name=\"phoneNumber\"\n            type=\"text\"\n          />\n        </label>\n      </div>\n      <div>\n        <label>\n          お名前\n          <input\n            name=\"name\"\n            type=\"text\"\n          />\n        </label>\n      </div>\n    </fieldset>\n    <fieldset>\n      <legend>\n        新しいお届け先を登録しますか？\n      </legend>\n      <label>\n        <input\n          name=\"registerDeliveryAddress\"\n          type=\"radio\"\n          value=\"0\"\n        />\n        いいえ\n      </label>\n      <label>\n        <input\n          name=\"registerDeliveryAddress\"\n          type=\"radio\"\n          value=\"1\"\n        />\n        はい\n      </label>\n    </fieldset>\n    <fieldset\n      disabled=\"\"\n      style=\"opacity: 0.3;\"\n    >\n      <legend>\n        過去のお届け先\n      </legend>\n      <select\n        name=\"pastDeliveryAddress\"\n      >\n        <option\n          value=\"address_id_xxxx\"\n        >\n          〒167-0051 東京都杉並区荻窪1-00-00\n        </option>\n      </select>\n    </fieldset>\n    <hr />\n    <div>\n      <button>\n        注文内容の確認へ進む\n      </button>\n    </div>\n  </form>\n</div>\n`;\n\nexports[`過去のお届け先がない場合 Snapshot 1`] = `\n<div>\n  <form>\n    <h2>\n      お届け先情報の入力\n    </h2>\n    <fieldset>\n      <legend>\n        連絡先\n      </legend>\n      <div>\n        <label>\n          電話番号\n          <input\n            name=\"phoneNumber\"\n            type=\"text\"\n          />\n        </label>\n      </div>\n      <div>\n        <label>\n          お名前\n          <input\n            name=\"name\"\n            type=\"text\"\n          />\n        </label>\n      </div>\n    </fieldset>\n    <fieldset>\n      <legend>\n        お届け先\n      </legend>\n      <div>\n        <label>\n          郵便番号\n          <input\n            name=\"postalCode\"\n            placeholder=\"167-0051\"\n            type=\"text\"\n          />\n        </label>\n      </div>\n      <div>\n        <label>\n          都道府県\n          <input\n            name=\"prefectures\"\n            placeholder=\"東京都\"\n            type=\"text\"\n          />\n        </label>\n      </div>\n      <div>\n        <label>\n          市区町村\n          <input\n            name=\"municipalities\"\n            placeholder=\"杉並区荻窪1\"\n            type=\"text\"\n          />\n        </label>\n      </div>\n      <div>\n        <label>\n          番地番号\n          <input\n            name=\"streetNumber\"\n            placeholder=\"00-00\"\n            type=\"text\"\n          />\n        </label>\n      </div>\n    </fieldset>\n    <hr />\n    <div>\n      <button>\n        注文内容の確認へ進む\n      </button>\n    </div>\n  </form>\n</div>\n`;\n"
  },
  {
    "path": "src/05/06/fixtures.ts",
    "content": "export const deliveryAddresses = [\n  {\n    id: \"address_id_xxxx\",\n    value: \"address_id_xxxx\",\n    children: \"〒167-0051 東京都杉並区荻窪1-00-00\",\n  },\n];\n"
  },
  {
    "path": "src/05/07/RegisterAddress.stories.tsx",
    "content": "import { ComponentMeta, ComponentStoryObj } from \"@storybook/react\";\nimport { RegisterAddress } from \"./RegisterAddress\";\n\nexport default {\n  component: RegisterAddress,\n} as ComponentMeta<typeof RegisterAddress>;\n\ntype Story = ComponentStoryObj<typeof RegisterAddress>;\n\nexport const Default: Story = {};\n"
  },
  {
    "path": "src/05/07/RegisterAddress.test.tsx",
    "content": "import { render, screen } from \"@testing-library/react\";\nimport { mockPostMyAddress } from \"./fetchers/mock\";\nimport { RegisterAddress } from \"./RegisterAddress\";\nimport {\n  clickSubmit,\n  inputContactNumber,\n  inputDeliveryAddress,\n} from \"./testingUtils\";\n\njest.mock(\"./fetchers\");\n\nasync function fillValuesAndSubmit() {\n  const contactNumber = await inputContactNumber();\n  const deliveryAddress = await inputDeliveryAddress();\n  const submitValues = { ...contactNumber, ...deliveryAddress };\n  await clickSubmit();\n  return submitValues;\n}\n\nasync function fillInvalidValuesAndSubmit() {\n  const contactNumber = await inputContactNumber({\n    name: \"田中 太郎\",\n    phoneNumber: \"abc-defg-hijkl\",\n  });\n  const deliveryAddress = await inputDeliveryAddress();\n  const submitValues = { ...contactNumber, ...deliveryAddress };\n  await clickSubmit();\n  return submitValues;\n}\n\nbeforeEach(() => {\n  jest.resetAllMocks();\n});\n\ntest(\"成功時「登録しました」が表示される\", async () => {\n  const mockFn = mockPostMyAddress();\n  render(<RegisterAddress />);\n  const submitValues = await fillValuesAndSubmit();\n  expect(mockFn).toHaveBeenCalledWith(expect.objectContaining(submitValues));\n  expect(screen.getByText(\"登録しました\")).toBeInTheDocument();\n});\n\ntest(\"失敗時「登録に失敗しました」が表示される\", async () => {\n  const mockFn = mockPostMyAddress(500);\n  render(<RegisterAddress />);\n  const submitValues = await fillValuesAndSubmit();\n  expect(mockFn).toHaveBeenCalledWith(expect.objectContaining(submitValues));\n  expect(screen.getByText(\"登録に失敗しました\")).toBeInTheDocument();\n});\n\ntest(\"バリデーションエラー時「不正な入力値が含まれています」が表示される\", async () => {\n  render(<RegisterAddress />);\n  await fillInvalidValuesAndSubmit();\n  expect(screen.getByText(\"不正な入力値が含まれています\")).toBeInTheDocument();\n});\n\ntest(\"不明なエラー時「不明なエラーが発生しました」が表示される\", async () => {\n  render(<RegisterAddress />);\n  await fillValuesAndSubmit();\n  expect(screen.getByText(\"不明なエラーが発生しました\")).toBeInTheDocument();\n});\n\ntest(\"Snapshot: 登録フォームが表示される\", async () => {\n  mockPostMyAddress();\n  // const mockFn = mockPostMyAddress();\n  const { container } = render(<RegisterAddress />);\n  // const submitValues = await fillValuesAndSubmit();\n  // expect(mockFn).toHaveBeenCalledWith(expect.objectContaining(submitValues));\n  expect(container).toMatchSnapshot();\n});\n"
  },
  {
    "path": "src/05/07/RegisterAddress.tsx",
    "content": "import { useState } from \"react\";\nimport { Form } from \"../06/Form\";\nimport { postMyAddress } from \"./fetchers\";\nimport { handleSubmit } from \"./handleSubmit\";\nimport { checkPhoneNumber, ValidationError } from \"./validations\";\n\nexport const RegisterAddress = () => {\n  const [postResult, setPostResult] = useState(\"\");\n  return (\n    <div>\n      <Form\n        onSubmit={handleSubmit((values) => {\n          try {\n            checkPhoneNumber(values.phoneNumber);\n            postMyAddress(values)\n              .then(() => {\n                setPostResult(\"登録しました\");\n              })\n              .catch(() => {\n                setPostResult(\"登録に失敗しました\");\n              });\n          } catch (err) {\n            if (err instanceof ValidationError) {\n              setPostResult(\"不正な入力値が含まれています\");\n              return;\n            }\n            setPostResult(\"不明なエラーが発生しました\");\n          }\n        })}\n      />\n      {postResult && <p>{postResult}</p>}\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/05/07/__snapshots__/RegisterAddress.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Snapshot: 登録フォームが表示される 1`] = `\n<div>\n  <div>\n    <form>\n      <h2>\n        お届け先情報の入力\n      </h2>\n      <fieldset>\n        <legend>\n          連絡先\n        </legend>\n        <div>\n          <label>\n            電話番号\n            <input\n              name=\"phoneNumber\"\n              type=\"text\"\n            />\n          </label>\n        </div>\n        <div>\n          <label>\n            お名前\n            <input\n              name=\"name\"\n              type=\"text\"\n            />\n          </label>\n        </div>\n      </fieldset>\n      <fieldset>\n        <legend>\n          お届け先\n        </legend>\n        <div>\n          <label>\n            郵便番号\n            <input\n              name=\"postalCode\"\n              placeholder=\"167-0051\"\n              type=\"text\"\n            />\n          </label>\n        </div>\n        <div>\n          <label>\n            都道府県\n            <input\n              name=\"prefectures\"\n              placeholder=\"東京都\"\n              type=\"text\"\n            />\n          </label>\n        </div>\n        <div>\n          <label>\n            市区町村\n            <input\n              name=\"municipalities\"\n              placeholder=\"杉並区荻窪1\"\n              type=\"text\"\n            />\n          </label>\n        </div>\n        <div>\n          <label>\n            番地番号\n            <input\n              name=\"streetNumber\"\n              placeholder=\"00-00\"\n              type=\"text\"\n            />\n          </label>\n        </div>\n      </fieldset>\n      <hr />\n      <div>\n        <button>\n          注文内容の確認へ進む\n        </button>\n      </div>\n    </form>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "src/05/07/fetchers/fixtures.ts",
    "content": "import type { HttpError, Result } from \"./type\";\n\nexport const httpError: HttpError = {\n  err: { message: \"internal server error\" },\n};\n\nexport const postMyAddressMock: Result = {\n  result: \"ok\",\n};\n"
  },
  {
    "path": "src/05/07/fetchers/index.ts",
    "content": "import { Result } from \"./type\";\n\nasync function handleResponse(res: Response) {\n  const data = await res.json();\n  if (!res.ok) {\n    throw data;\n  }\n  return data;\n}\n\nconst host = (path: string) => `https://myapi.testing.com${path}`;\n\nconst headers = {\n  Accept: \"application/json\",\n  \"Content-Type\": \"application/json\",\n};\n\nexport function postMyAddress(values: unknown): Promise<Result> {\n  return fetch(host(\"/my/address\"), {\n    method: \"POST\",\n    body: JSON.stringify(values),\n    headers,\n  }).then(handleResponse);\n}\n"
  },
  {
    "path": "src/05/07/fetchers/mock.ts",
    "content": "import * as Fetchers from \".\";\nimport { httpError, postMyAddressMock } from \"./fixtures\";\n\nexport function mockPostMyAddress(status = 201) {\n  if (status > 299) {\n    return jest\n      .spyOn(Fetchers, \"postMyAddress\")\n      .mockRejectedValueOnce(httpError);\n  }\n  return jest\n    .spyOn(Fetchers, \"postMyAddress\")\n    .mockResolvedValueOnce(postMyAddressMock);\n}\n"
  },
  {
    "path": "src/05/07/fetchers/type.ts",
    "content": "export type HttpError = {\n  err: { message: string };\n};\n\nexport type Result = {\n  result: string;\n};\n"
  },
  {
    "path": "src/05/07/handleSubmit.ts",
    "content": "export function handleSubmit(callback: (values: any) => Promise<void> | void) {\n  return (event: React.FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n    const formData = new FormData(event.currentTarget);\n    const values: { [k: string]: unknown } = {};\n    formData.forEach((value, key) => (values[key] = value));\n    return callback(values);\n  };\n}\n"
  },
  {
    "path": "src/05/07/testingUtils.ts",
    "content": "import { screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\n\nconst user = userEvent.setup();\n\nexport function getGroupByName(name: string) {\n  return screen.getByRole(\"group\", { name });\n}\n\nexport async function inputContactNumber(\n  inputValues = {\n    name: \"田中 太郎\",\n    phoneNumber: \"000-0000-0000\",\n  }\n) {\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"電話番号\" }),\n    inputValues.phoneNumber\n  );\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"お名前\" }),\n    inputValues.name\n  );\n  return inputValues;\n}\n\nexport async function inputDeliveryAddress(\n  inputValues = {\n    postalCode: \"167-0051\",\n    prefectures: \"東京都\",\n    municipalities: \"杉並区荻窪1\",\n    streetNumber: \"00-00\",\n  }\n) {\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"郵便番号\" }),\n    inputValues.postalCode\n  );\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"都道府県\" }),\n    inputValues.prefectures\n  );\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"市区町村\" }),\n    inputValues.municipalities\n  );\n  await user.type(\n    screen.getByRole(\"textbox\", { name: \"番地番号\" }),\n    inputValues.streetNumber\n  );\n  return inputValues;\n}\n\nexport async function clickSubmit() {\n  await user.click(\n    screen.getByRole(\"button\", { name: \"注文内容の確認へ進む\" })\n  );\n}\n"
  },
  {
    "path": "src/05/07/validations.ts",
    "content": "export class ValidationError extends Error {}\n\nexport function checkPhoneNumber(value: any) {\n  if (!value.match(/^[0-9\\-]+$/)) {\n    throw new ValidationError();\n  }\n}\n"
  },
  {
    "path": "src/06/Articles.test.tsx",
    "content": "import { render, screen } from \"@testing-library/react\";\nimport { Articles } from \"./Articles\";\n\nxtest(\"読み込み中の場合「..loading」が表示される\", () => {\n  render(<Articles items={[]} isLoading={true} />);\n  expect(screen.getByText(\"...loading\")).toBeInTheDocument();\n});\n\nxtest(\"一覧要素が空の場合「投稿記事がありません」が表示される\", () => {\n  render(<Articles items={[]} isLoading={false} />);\n  expect(screen.getByText(\"投稿記事がありません\")).toBeInTheDocument();\n});\n\ntest(\"一覧要素がある場合、一覧が表示される\", () => {\n  const items = [\n    { id: 1, title: \"Testing Next.js\" },\n    { id: 2, title: \"Storybook play function\" },\n    { id: 3, title: \"Visual Regression Testing \" },\n  ];\n  render(<Articles items={items} isLoading={false} />);\n  expect(screen.getByRole(\"list\")).toBeInTheDocument();\n});\n"
  },
  {
    "path": "src/06/Articles.tsx",
    "content": "type Props = {\n  items: { id: number; title: string }[];\n  isLoading?: boolean;\n};\nexport const Articles = ({ items, isLoading }: Props) => {\n  if (isLoading) {\n    return <p>...loading</p>;\n  }\n  return (\n    <div>\n      <h2>記事一覧</h2>\n      {items.length ? (\n        <ul>\n          {items.map((item) => (\n            <li key={item.id}>\n              <a href={`/articles/${item.id}`}>{item.title}</a>\n            </li>\n          ))}\n        </ul>\n      ) : (\n        <p>投稿記事がありません</p>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/06/greetByTime.test.ts",
    "content": "import { greetByTime } from \"./greetByTime\";\n\ndescribe(\"greetByTime(\", () => {\n  beforeEach(() => {\n    jest.useFakeTimers();\n  });\n  afterEach(() => {\n    jest.useRealTimers();\n  });\n  // (1) 「おはよう」を返す関数\n  test(\"朝は「おはよう」を返す\", () => {\n    jest.setSystemTime(new Date(2023, 4, 23, 8, 0, 0));\n    expect(greetByTime()).toBe(\"おはよう\");\n  });\n  // (2) 「こんにちは」を返す関数\n  xtest(\"昼は「こんにちは」を返す\", () => {\n    jest.setSystemTime(new Date(2023, 4, 23, 14, 0, 0));\n    expect(greetByTime()).toBe(\"こんにちは\");\n  });\n  // (3) 「こんばんは」を返す関数\n  xtest(\"夜は「こんばんは」を返す\", () => {\n    jest.setSystemTime(new Date(2023, 4, 23, 21, 0, 0));\n    expect(greetByTime()).toBe(\"こんばんは\");\n  });\n});\n"
  },
  {
    "path": "src/06/greetByTime.ts",
    "content": "export function greetByTime() {\n  const hour = new Date().getHours();\n  if (hour < 12) {\n    return \"おはよう\";\n  } else if (hour < 18) {\n    return \"こんにちは\";\n  }\n  return \"こんばんは\";\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    /* Visit https://aka.ms/tsconfig to read more about this file */\n\n    /* Projects */\n    // \"incremental\": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */\n    // \"composite\": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */\n    // \"tsBuildInfoFile\": \"./.tsbuildinfo\",              /* Specify the path to .tsbuildinfo incremental compilation file. */\n    // \"disableSourceOfProjectReferenceRedirect\": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */\n    // \"disableSolutionSearching\": true,                 /* Opt a project out of multi-project reference checking when editing. */\n    // \"disableReferencedProjectLoad\": true,             /* Reduce the number of projects loaded automatically by TypeScript. */\n\n    /* Language and Environment */\n    \"target\": \"es2016\",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */\n    // \"lib\": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */\n    \"jsx\": \"preserve\",                                /* Specify what JSX code is generated. */\n    // \"experimentalDecorators\": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */\n    // \"emitDecoratorMetadata\": true,                    /* Emit design-type metadata for decorated declarations in source files. */\n    // \"jsxFactory\": \"\",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */\n    // \"jsxFragmentFactory\": \"\",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */\n    // \"jsxImportSource\": \"\",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */\n    // \"reactNamespace\": \"\",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */\n    // \"noLib\": true,                                    /* Disable including any library files, including the default lib.d.ts. */\n    // \"useDefineForClassFields\": true,                  /* Emit ECMAScript-standard-compliant class fields. */\n    // \"moduleDetection\": \"auto\",                        /* Control what method is used to detect module-format JS files. */\n\n    /* Modules */\n    \"module\": \"commonjs\",                                /* Specify what module code is generated. */\n    // \"rootDir\": \"./\",                                  /* Specify the root folder within your source files. */\n    // \"moduleResolution\": \"node\",                       /* Specify how TypeScript looks up a file from a given module specifier. */\n    // \"baseUrl\": \"./\",                                  /* Specify the base directory to resolve non-relative module names. */\n    // \"paths\": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */\n    // \"rootDirs\": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */\n    // \"typeRoots\": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */\n    // \"types\": [],                                      /* Specify type package names to be included without being referenced in a source file. */\n    // \"allowUmdGlobalAccess\": true,                     /* Allow accessing UMD globals from modules. */\n    // \"moduleSuffixes\": [],                             /* List of file name suffixes to search when resolving a module. */\n    // \"resolveJsonModule\": true,                        /* Enable importing .json files. */\n    // \"noResolve\": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */\n\n    /* JavaScript Support */\n    // \"allowJs\": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */\n    // \"checkJs\": true,                                  /* Enable error reporting in type-checked JavaScript files. */\n    // \"maxNodeModuleJsDepth\": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */\n\n    /* Emit */\n    // \"declaration\": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */\n    // \"declarationMap\": true,                           /* Create sourcemaps for d.ts files. */\n    // \"emitDeclarationOnly\": true,                      /* Only output d.ts files and not JavaScript files. */\n    // \"sourceMap\": true,                                /* Create source map files for emitted JavaScript files. */\n    // \"outFile\": \"./\",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */\n    // \"outDir\": \"./\",                                   /* Specify an output folder for all emitted files. */\n    // \"removeComments\": true,                           /* Disable emitting comments. */\n    // \"noEmit\": true,                                   /* Disable emitting files from a compilation. */\n    // \"importHelpers\": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */\n    // \"importsNotUsedAsValues\": \"remove\",               /* Specify emit/checking behavior for imports that are only used for types. */\n    // \"downlevelIteration\": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */\n    // \"sourceRoot\": \"\",                                 /* Specify the root path for debuggers to find the reference source code. */\n    // \"mapRoot\": \"\",                                    /* Specify the location where debugger should locate map files instead of generated locations. */\n    // \"inlineSourceMap\": true,                          /* Include sourcemap files inside the emitted JavaScript. */\n    // \"inlineSources\": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */\n    // \"emitBOM\": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */\n    // \"newLine\": \"crlf\",                                /* Set the newline character for emitting files. */\n    // \"stripInternal\": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */\n    // \"noEmitHelpers\": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */\n    // \"noEmitOnError\": true,                            /* Disable emitting files if any type checking errors are reported. */\n    // \"preserveConstEnums\": true,                       /* Disable erasing 'const enum' declarations in generated code. */\n    // \"declarationDir\": \"./\",                           /* Specify the output directory for generated declaration files. */\n    // \"preserveValueImports\": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */\n\n    /* Interop Constraints */\n    // \"isolatedModules\": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */\n    // \"allowSyntheticDefaultImports\": true,             /* Allow 'import x from y' when a module doesn't have a default export. */\n    \"esModuleInterop\": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */\n    // \"preserveSymlinks\": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */\n    \"forceConsistentCasingInFileNames\": true,            /* Ensure that casing is correct in imports. */\n\n    /* Type Checking */\n    \"strict\": true,                                      /* Enable all strict type-checking options. */\n    // \"noImplicitAny\": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */\n    // \"strictNullChecks\": true,                         /* When type checking, take into account 'null' and 'undefined'. */\n    // \"strictFunctionTypes\": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */\n    // \"strictBindCallApply\": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */\n    // \"strictPropertyInitialization\": true,             /* Check for class properties that are declared but not set in the constructor. */\n    // \"noImplicitThis\": true,                           /* Enable error reporting when 'this' is given the type 'any'. */\n    // \"useUnknownInCatchVariables\": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */\n    // \"alwaysStrict\": true,                             /* Ensure 'use strict' is always emitted. */\n    // \"noUnusedLocals\": true,                           /* Enable error reporting when local variables aren't read. */\n    // \"noUnusedParameters\": true,                       /* Raise an error when a function parameter isn't read. */\n    // \"exactOptionalPropertyTypes\": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */\n    // \"noImplicitReturns\": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */\n    // \"noFallthroughCasesInSwitch\": true,               /* Enable error reporting for fallthrough cases in switch statements. */\n    // \"noUncheckedIndexedAccess\": true,                 /* Add 'undefined' to a type when accessed using an index. */\n    // \"noImplicitOverride\": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */\n    // \"noPropertyAccessFromIndexSignature\": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */\n    // \"allowUnusedLabels\": true,                        /* Disable error reporting for unused labels. */\n    // \"allowUnreachableCode\": true,                     /* Disable error reporting for unreachable code. */\n\n    /* Completeness */\n    // \"skipDefaultLibCheck\": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */\n    \"skipLibCheck\": true                                 /* Skip type checking all .d.ts files. */\n  }\n}\n"
  }
]