Cucumber.jsをts-node/esmで動かしてみたメモ

# 概要
AIに７割書いてもらったが、微調整が必要だったのでメモしておく。
[ソースコード](https://github.com/hibohiboo/odyssage/blob/1c3b863a337ed235c3128b22c596842a1403cb8a/packages/core/cucumber.mjs)

# ソースコード
```json:packages/core/package.json
{
  "name": "@odyssage/core",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "test": "cucumber-js --config cucumber.mjs",
    "ncu": "ncu -u"
  },
  "devDependencies": {
    "@cucumber/cucumber": "^11.0.1",
    "@types/node": "^22.9.0",
    "ts-node": "^10.9.2",
    "vitest": "^2.1.4"
  }
}

```
```js:packages/core/cucumber.mjs
export default {
  paths: ['**/features/*.feature'],
  import: ['**/step-definitions/*.step.ts'],
  loader: ['ts-node/esm'],
}
```

```gerkins:packages/core/character/tests/bdd/features/character.feature
Feature: キャラクターの作成

  Scenario: 新しいキャラクターを作成する
    Given ユーザーがキャラクター作成フォームを開いている
    When "太郎" という名前でキャラクターを作成する
    Then キャラクターリストに "太郎" が表示されている

  Scenario: キャラクターにタグを追加する
    Given キャラクター "太郎" が存在する
    When "力持ち" のタグを追加する
    Then "太郎" のタグリストに "力持ち" が含まれている

```

```ts:packages/core/character/tests/bdd/step-definitions/character.step.ts
import { When, Then, Given } from '@cucumber/cucumber';
// Node.js は Native ESM モードでは拡張子を補完しない
import { CharacterService } from '@odyssage/core/character/application/CharacterService.ts';
import { Character } from '@odyssage/core/character/domain/Character.ts';
import { Tag } from '@odyssage/core/character/domain/Tag.ts';
import { CharacterRepository } from '@odyssage/core/character/infrastructure/CharacterRepository.ts';
import { strict as assert } from 'assert';

let character: Character;
let repository: CharacterRepository;
let service: CharacterService;

Given('ユーザーがキャラクター作成フォームを開いている', () => {
  repository = new CharacterRepository();
  service = new CharacterService(repository);
});

When(/"(.+)" という名前でキャラクターを作成する/, (name: string) => {
  character = { id: '1', name, tags: [], extendedTags: [] };
  repository.add(character);
});

Then(/キャラクターリストに "(.+)" が表示されている/, (name: string) => {
  const characters = repository.listAll();
  const found = characters.some((c) => c.name === name);
  assert(found, `キャラクターリストに "${name}" が表示されていません`);
});

Given(/キャラクター "(.+)" が存在する/, (name: string) => {
  character = { id: '1', name, tags: [], extendedTags: [] };
  repository.add(character);
});

When(/"(.+)" のタグを追加する/, (tag: string) => {
  const newTag: Tag = tag;
  service.addTag(character.id, newTag);
});

Then(
  /"(.+)" のタグリストに "(.+)" が含まれている/,
  (name: string, tag: string) => {
    const character = repository.findById('1');
    assert(character, `キャラクター "${name}" が存在しません`);
    const found = character.tags.some((t) => t === tag);
    assert(found, `"${name}" のタグリストに "${tag}" が含まれていません`);
  },
);

```

# 2024.12.04 追記

テーブルもかけるみたい。

```:characters.feature
Feature: キャラクターリストの表示
  As a プレイヤー
  I want to view all characters
  So that I can select and use them

  Scenario: キャラクターリストを取得する
    Given 以下のキャラクターが存在する:
      | name |
      | 勇者 |
      | 魔法使い |
    When キャラクターリストを取得する
    Then 以下のキャラクターがリストに表示されるべき:
      | name      |
      | 勇者      |
      | 魔法使い  |
```

```ts:characters.steps.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
import { strict as assert } from 'assert';
import { Given, When, Then } from '@cucumber/cucumber';
import { Character } from '@odyssage/core/character/domain/Character.ts';
import { CharacterRepository } from '@odyssage/core/character/infrastructure/CharacterRepository.ts';

let characterRepository: CharacterRepository;
let result: { name: string }[];

Given('以下のキャラクターが存在する:', (dataTable) => {
  characterRepository = new CharacterRepository();
  const characters = dataTable.hashes();
  characters.forEach((character: any) => {
    characterRepository.add(
      new Character({
        id: character.name,
        name: character.name,
        tags: [],
        extendedTags: [],
      }),
    );
  });
});

When('キャラクターリストを取得する', async () => {
  result = await characterRepository.listAll();
});

Then('以下のキャラクターがリストに表示されるべき:', (dataTable) => {
  const expectedCharacters = dataTable.hashes().map((row: any) => row.name);
  const actualCharacters = result.map((character) => character.name);

  assert.deepEqual(actualCharacters, expectedCharacters);
});

```

[ソースコード](https://github.com/hibohiboo/odyssage/blob/1f527aff28a23c1464d8217441a0c134ccbfb8a2/packages/core/character/tests/bdd/step-definitions/characters.steps.ts)