/* eslint-disable @typescript-eslint/require-await */
export const description = `
Unit tests for TestGroup.
`;

import { Fixture } from '../common/framework/fixture.js';
import { makeTestGroup } from '../common/framework/test_group.js';
import { TestQueryMultiFile } from '../common/internal/query/query.js';
import { kQueryMaxLength, makeTestGroupForUnitTesting } from '../common/internal/test_group.js';
import { assert } from '../common/util/util.js';

import { TestGroupTest } from './test_group_test.js';
import { UnitTest } from './unit_test.js';

export const g = makeTestGroup(TestGroupTest);

g.test('UnitTest_fixture').fn(async t0 => {
  let seen = 0;
  function count(_t: Fixture): void {
    seen++;
  }

  const g = makeTestGroupForUnitTesting(UnitTest);

  g.test('test').fn(count);
  g.test('testp')
    .paramsSimple([{ a: 1 }])
    .fn(count);

  await t0.run(g);
  t0.expect(seen === 2);
});

g.test('custom_fixture').fn(async t0 => {
  let seen = 0;
  class Counter extends UnitTest {
    count(): void {
      seen++;
    }
  }

  const g = makeTestGroupForUnitTesting(Counter);

  g.test('test').fn(t => {
    t.count();
  });
  g.test('testp')
    .paramsSimple([{ a: 1 }])
    .fn(t => {
      t.count();
    });

  await t0.run(g);
  t0.expect(seen === 2);
});

g.test('stack').fn(async t0 => {
  const g = makeTestGroupForUnitTesting(UnitTest);

  const doNestedThrow1 = () => {
    throw new Error('goodbye');
  };

  const doNestedThrow2 = () => doNestedThrow1();

  g.test('fail').fn(t => {
    t.fail();
  });
  g.test('throw').fn(_t => {
    throw new Error('hello');
  });
  g.test('throw_nested').fn(_t => {
    doNestedThrow2();
  });

  const res = await t0.run(g);

  const search = /unittests[/\\]test_group\.spec\.[tj]s/;
  t0.expect(res.size > 0);
  for (const { logs } of res.values()) {
    assert(logs !== undefined, 'expected logs');
    t0.expect(logs.some(l => search.test(l.toJSON())));
    t0.expect(search.test(logs[logs.length - 1].toJSON()));
  }
});

g.test('no_fn').fn(t => {
  const g = makeTestGroupForUnitTesting(UnitTest);

  g.test('missing');

  t.shouldThrow('Error', () => {
    g.validate(new TestQueryMultiFile('s', ['f']));
  });
});

g.test('duplicate_test_name').fn(t => {
  const g = makeTestGroupForUnitTesting(UnitTest);
  g.test('abc').fn(() => {});

  t.shouldThrow('Error', () => {
    g.test('abc').fn(() => {});
  });
});

g.test('duplicate_test_params,none').fn(() => {
  {
    const g = makeTestGroupForUnitTesting(UnitTest);
    g.test('abc')
      .paramsSimple([])
      .fn(() => {});
    g.validate(new TestQueryMultiFile('s', ['f']));
  }

  {
    const g = makeTestGroupForUnitTesting(UnitTest);
    g.test('abc').fn(() => {});
    g.validate(new TestQueryMultiFile('s', ['f']));
  }

  {
    const g = makeTestGroupForUnitTesting(UnitTest);
    g.test('abc')
      .paramsSimple([
        { a: 1 }, //
      ])
      .fn(() => {});
    g.validate(new TestQueryMultiFile('s', ['f']));
  }
});

g.test('duplicate_test_params,basic').fn(t => {
  {
    const g = makeTestGroupForUnitTesting(UnitTest);
    const builder = g.test('abc');
    t.shouldThrow('Error', () => {
      builder.paramsSimple([
        { a: 1 }, //
        { a: 1 },
      ]);
      g.validate(new TestQueryMultiFile('s', ['f']));
    });
  }
  {
    const g = makeTestGroupForUnitTesting(UnitTest);
    g.test('abc')
      .params(u =>
        u.expandWithParams(() => [
          { a: 1 }, //
          { a: 1 },
        ])
      )
      .fn(() => {});
    t.shouldThrow('Error', () => {
      g.validate(new TestQueryMultiFile('s', ['f']));
    });
  }
  {
    const g = makeTestGroupForUnitTesting(UnitTest);
    g.test('abc')
      .paramsSimple([
        { a: 1, b: 3 }, //
        { b: 3, a: 1 },
      ])
      .fn(() => {});
    t.shouldThrow('Error', () => {
      g.validate(new TestQueryMultiFile('s', ['f']));
    });
  }
});

g.test('duplicate_test_params,with_different_private_params').fn(t => {
  {
    const g = makeTestGroupForUnitTesting(UnitTest);
    const builder = g.test('abc');
    t.shouldThrow('Error', () => {
      builder.paramsSimple([
        { a: 1, _b: 1 }, //
        { a: 1, _b: 2 },
      ]);
    });
  }
  {
    const g = makeTestGroupForUnitTesting(UnitTest);
    g.test('abc')
      .params(u =>
        u.expandWithParams(() => [
          { a: 1, _b: 1 }, //
          { a: 1, _b: 2 },
        ])
      )
      .fn(() => {});
    t.shouldThrow('Error', () => {
      g.validate(new TestQueryMultiFile('s', ['f']));
    });
  }
});

g.test('invalid_test_name').fn(t => {
  const g = makeTestGroupForUnitTesting(UnitTest);

  const badChars = Array.from('"`~@#$+=\\|!^&*[]<>{}-\'. ');
  for (const char of badChars) {
    const name = 'a' + char + 'b';
    t.shouldThrow(
      'Error',
      () => {
        g.test(name).fn(() => {});
      },
      { message: name }
    );
  }
});

g.test('long_test_query,long_test_name').fn(t => {
  const g = makeTestGroupForUnitTesting(UnitTest);

  const long = Array(kQueryMaxLength - 5).join('a');

  const fileQuery = new TestQueryMultiFile('s', ['f']);
  g.test(long).unimplemented();
  g.validate(fileQuery);

  g.test(long + 'a').unimplemented();
  t.shouldThrow(
    'Error',
    () => {
      g.validate(fileQuery);
    },
    { message: long }
  );
});

g.test('long_case_query,long_test_name').fn(t => {
  const g = makeTestGroupForUnitTesting(UnitTest);

  const long = Array(kQueryMaxLength - 5).join('a');

  const fileQuery = new TestQueryMultiFile('s', ['f']);
  g.test(long).fn(() => {});
  g.validate(fileQuery);

  g.test(long + 'a').fn(() => {});
  t.shouldThrow(
    'Error',
    () => {
      g.validate(fileQuery);
    },
    { message: long }
  );
});

g.test('long_case_query,long_case_name').fn(t => {
  const g = makeTestGroupForUnitTesting(UnitTest);

  const long = Array(kQueryMaxLength - 9).join('a');

  const fileQuery = new TestQueryMultiFile('s', ['f']);
  g.test('t')
    .paramsSimple([{ x: long }])
    .fn(() => {});
  g.validate(fileQuery);

  g.test('u')
    .paramsSimple([{ x: long + 'a' }])
    .fn(() => {});
  t.shouldThrow(
    'Error',
    () => {
      g.validate(fileQuery);
    },
    { message: long }
  );
});

g.test('param_value,valid').fn(() => {
  const g = makeTestGroup(UnitTest);
  g.test('a').paramsSimple([{ x: JSON.stringify({ a: 1, b: 2 }) }]);
});

g.test('param_value,invalid').fn(t => {
  for (const badChar of ';=*') {
    const g = makeTestGroupForUnitTesting(UnitTest);
    const builder = g.test('a');
    t.shouldThrow('Error', () => {
      builder.paramsSimple([{ badChar }]);
    });
  }
});

g.test('subcases').fn(async t0 => {
  const g = makeTestGroupForUnitTesting(UnitTest);
  g.test('a')
    .paramsSubcasesOnly(u =>
      u //
        .combineWithParams([{ a: 1 }])
    )
    .fn(t => {
      t.expect(t.params.a === 1, 'a must be 1');
    });

  function* gen({ a, b }: { a?: number; b?: number }) {
    if (b === 2) {
      yield { ret: 2 };
    } else if (a === 1) {
      yield { ret: 1 };
    } else {
      yield { ret: -1 };
    }
  }
  g.test('b')
    .params(u =>
      u
        .combineWithParams([{ a: 1 }, { b: 2 }])
        .beginSubcases()
        .expandWithParams(gen)
    )
    .fn(t => {
      const { a, b, ret } = t.params;
      t.expect((a === 1 && ret === 1) || (b === 2 && ret === 2));
    });

  const result = await t0.run(g);
  t0.expect(Array.from(result.values()).every(v => v.status === 'pass'));
});

g.test('subcases,skip')
  .desc(
    'If all tests are skipped then status is "skip". If at least one test passed, status is "pass"'
  )
  .params(u => u.combine('allSkip', [false, true]))
  .fn(async t0 => {
    const { allSkip } = t0.params;
    const g = makeTestGroupForUnitTesting(UnitTest);
    g.test('a')
      .params(u => u.beginSubcases().combine('do', ['pass', 'skip', 'pass']))
      .fn(t => {
        t.skipIf(allSkip || t.params.do === 'skip');
      });
    const result = await t0.run(g);
    const values = Array.from(result.values());
    t0.expect(values.length === 1);
    const expectedStatus = allSkip ? 'skip' : 'pass';
    t0.expect(
      values[0].status === expectedStatus,
      `expect: ${values[0].status} === ${expectedStatus}, allSkip: ${allSkip}`
    );
  });

g.test('exceptions')
  .params(u =>
    u
      .combine('useSubcases', [false, true]) //
      .combine('useDOMException', [false, true])
  )
  .fn(async t0 => {
    const { useSubcases, useDOMException } = t0.params;
    const g = makeTestGroupForUnitTesting(UnitTest);

    const b1 = g.test('a');
    let b2;
    if (useSubcases) {
      b2 = b1.paramsSubcasesOnly(u => u);
    } else {
      b2 = b1.params(u => u);
    }
    b2.fn(_t => {
      if (useDOMException) {
        throw new DOMException('Message!', 'Name!');
      } else {
        throw new Error('Message!');
      }
    });

    const result = await t0.run(g);
    const values = Array.from(result.values());
    t0.expect(values.length === 1);
    t0.expect(values[0].status === 'fail');
  });

g.test('throws').fn(async t0 => {
  const g = makeTestGroupForUnitTesting(UnitTest);

  g.test('a').fn(_t => {
    throw new Error();
  });

  const result = await t0.run(g);
  const values = Array.from(result.values());
  t0.expect(values.length === 1);
  t0.expect(values[0].status === 'fail');
});

g.test('shouldThrow').fn(async t0 => {
  t0.shouldThrow('TypeError', () => {
    throw new TypeError();
  });

  const g = makeTestGroupForUnitTesting(UnitTest);

  g.test('a').fn(t => {
    t.shouldThrow('Error', () => {
      throw new TypeError();
    });
  });

  const result = await t0.run(g);
  const values = Array.from(result.values());
  t0.expect(values.length === 1);
  t0.expect(values[0].status === 'fail');
});

g.test('shouldReject').fn(async t0 => {
  t0.shouldReject(
    'TypeError',
    (async () => {
      throw new TypeError();
    })()
  );

  const g = makeTestGroupForUnitTesting(UnitTest);

  g.test('a').fn(t => {
    t.shouldReject(
      'Error',
      (async () => {
        throw new TypeError();
      })()
    );
  });

  const result = await t0.run(g);
  // Fails even though shouldReject doesn't fail until after the test function ends
  const values = Array.from(result.values());
  t0.expect(values.length === 1);
  t0.expect(values[0].status === 'fail');
});
