"use strict";

const path = require("node:path");
const {
  touchFile,
  runMochaAsync,
  runMochaWatchAsync,
  copyFixture,
  DEFAULT_FIXTURE,
  invokeMochaAsync,
  resolveFixturePath,
  createTempDir,
} = require("../helpers.cjs");

describe("global setup/teardown", function () {
  const watchBudgetMs = 8500;

  describe("when mocha run in serial mode", function () {
    it("should execute global setup and teardown", async function () {
      return expect(
        runMochaAsync(DEFAULT_FIXTURE, [
          "--require",
          resolveFixturePath(
            "plugins/global-fixtures/global-setup-teardown.fixture.cjs",
          ),
        ]),
        "when fulfilled",
        "to have passed",
      );
    });

    describe("when only global teardown is supplied", function () {
      it("should run global teardown", async function () {
        return expect(
          runMochaAsync(DEFAULT_FIXTURE, [
            "--require",
            resolveFixturePath(
              "plugins/global-fixtures/global-teardown.fixture.cjs",
            ),
          ]),
          "when fulfilled",
          "to contain once",
          /teardown schmeardown/,
        );
      });
    });

    describe("when only global setup is supplied", function () {
      it("should run global setup", async function () {
        return expect(
          runMochaAsync(DEFAULT_FIXTURE, [
            "--require",
            resolveFixturePath(
              "plugins/global-fixtures/global-setup.fixture.cjs",
            ),
          ]),
          "when fulfilled",
          "to contain once",
          /setup schmetup/,
        );
      });
    });

    it("should share context", async function () {
      return expect(
        runMochaAsync(DEFAULT_FIXTURE, [
          "--require",
          resolveFixturePath(
            "plugins/global-fixtures/global-setup-teardown.fixture.cjs",
          ),
        ]),
        "when fulfilled",
        "to contain once",
        /setup: this\.foo = bar[\s\S]+teardown: this\.foo = bar/,
      );
    });

    describe("when supplied multiple functions", function () {
      it("should execute them sequentially", async function () {
        return expect(
          runMochaAsync(DEFAULT_FIXTURE, [
            "--require",
            resolveFixturePath(
              "plugins/global-fixtures/global-setup-teardown-multiple.fixture.cjs",
            ),
          ]),
          "when fulfilled",
          "to contain once",
          /teardown: this.foo = 3/,
        );
      });
    });

    describe("when a global fixture throws", function () {
      async function runWithFixture(fixturePath, extraArgs = []) {
        const [, promise] = invokeMochaAsync(
          [
            "--require",
            resolveFixturePath(fixturePath),
            resolveFixturePath(DEFAULT_FIXTURE),
            ...extraArgs,
          ],
          { stdio: "pipe" },
        );
        return promise;
      }

      it("should handle when global teardown throws", async function () {
        const res = await runWithFixture(
          "plugins/global-fixtures/global-teardown-throws.fixture.cjs",
        );
        expect(
          res,
          "to have failed with output",
          /\[mocha\] global teardown failed[\s\S]*teardown kaboom/,
        );
        expect(res.output, "to match", /setup ran/);
        expect(res.output, "to match", /1 passing/);
      });

      it("should handle when global setup throws", async function () {
        const res = await runWithFixture(
          "plugins/global-fixtures/global-setup-throws.fixture.cjs",
        );
        expect(
          res,
          "to have failed with output",
          /\[mocha\] global setup failed[\s\S]*setup kaboom/,
        );
        expect(res.output, "not to match", /teardown should not run/);
        expect(res.output, "not to match", /passing/);
      });
    });

    describe("when run in watch mode", function () {
      let tempDir;
      let testFile;
      let removeTempDir;

      beforeEach(async function () {
        const tempInfo = await createTempDir();
        tempDir = tempInfo.dirpath;
        removeTempDir = tempInfo.removeTempDir;
        testFile = path.join(tempDir, "test.js");
        copyFixture(DEFAULT_FIXTURE, testFile);
      });

      afterEach(async function () {
        if (removeTempDir) {
          return removeTempDir();
        }
      });

      it("should execute global setup and teardown", async function () {
        return expect(
          runMochaWatchAsync(
            [
              "--require",
              resolveFixturePath(
                "plugins/global-fixtures/global-setup-teardown.fixture.cjs",
              ),
              testFile,
            ],
            { cwd: tempDir, budgetMs: watchBudgetMs },
            () => {
              touchFile(testFile);
            },
          ),
          "when fulfilled",
          "to have passed",
        );
      });

      describe("when only global teardown is supplied", function () {
        it("should run global teardown", async function () {
          return expect(
            runMochaWatchAsync(
              [
                "--require",
                resolveFixturePath(
                  "plugins/global-fixtures/global-teardown.fixture.cjs",
                ),
                testFile,
              ],
              { cwd: tempDir, budgetMs: watchBudgetMs },
              () => {
                touchFile(testFile);
              },
            ),
            "when fulfilled",
            "to contain once",
            /teardown schmeardown/,
          );
        });
      });

      describe("when only global setup is supplied", function () {
        it("should run global setup", async function () {
          return expect(
            runMochaWatchAsync(
              [
                "--require",
                resolveFixturePath(
                  "plugins/global-fixtures/global-setup.fixture.cjs",
                ),
                testFile,
              ],
              { cwd: tempDir, budgetMs: watchBudgetMs },
              () => {
                touchFile(testFile);
              },
            ),
            "when fulfilled",
            "to contain once",
            /setup schmetup/,
          );
        });
      });

      it("should not re-execute the global fixtures", async function () {
        return expect(
          runMochaWatchAsync(
            [
              "--require",
              resolveFixturePath(
                "plugins/global-fixtures/global-setup-teardown-multiple.fixture.cjs",
              ),
              testFile,
            ],
            { cwd: tempDir, budgetMs: watchBudgetMs },
            () => {
              touchFile(testFile);
            },
          ),
          "when fulfilled",
          "to contain once",
          /teardown: this.foo = 3/,
        );
      });
    });
  });

  describe("when mocha run in parallel mode", function () {
    it("should execute global setup and teardown", async function () {
      return expect(
        runMochaAsync(DEFAULT_FIXTURE, [
          "--parallel",
          "--require",
          resolveFixturePath(
            "plugins/global-fixtures/global-setup-teardown.fixture.cjs",
          ),
        ]),
        "when fulfilled",
        "to have passed",
      );
    });

    it("should share context", async function () {
      return expect(
        runMochaAsync(DEFAULT_FIXTURE, [
          "--parallel",
          "--require",
          resolveFixturePath(
            "plugins/global-fixtures/global-setup-teardown.fixture.cjs",
          ),
        ]),
        "when fulfilled",
        "to contain once",
        /setup: this.foo = bar/,
      ).and("when fulfilled", "to contain once", /teardown: this.foo = bar/);
    });

    describe("when supplied multiple functions", function () {
      it("should execute them sequentially", async function () {
        return expect(
          runMochaAsync(DEFAULT_FIXTURE, [
            "--parallel",
            "--require",
            resolveFixturePath(
              "plugins/global-fixtures/global-setup-teardown-multiple.fixture.cjs",
            ),
          ]),
          "when fulfilled",
          "to contain once",
          /teardown: this.foo = 3/,
        );
      });
    });

    describe("when run in watch mode", function () {
      let tempDir;
      let testFile;
      let removeTempDir;

      beforeEach(async function () {
        const tempInfo = await createTempDir();
        tempDir = tempInfo.dirpath;
        removeTempDir = tempInfo.removeTempDir;
        testFile = path.join(tempDir, "test.js");
        copyFixture(DEFAULT_FIXTURE, testFile);
      });

      afterEach(async function () {
        if (removeTempDir) {
          return removeTempDir();
        }
      });

      it("should execute global setup and teardown", async function () {
        return expect(
          runMochaWatchAsync(
            [
              "--parallel",
              "--require",
              resolveFixturePath(
                "plugins/global-fixtures/global-setup-teardown.fixture.cjs",
              ),
              testFile,
            ],
            { cwd: tempDir, budgetMs: watchBudgetMs },
            () => {
              touchFile(testFile);
            },
          ),
          "when fulfilled",
          "to have passed",
        );
      });

      it("should not re-execute the global fixtures", async function () {
        return expect(
          runMochaWatchAsync(
            [
              "--parallel",
              "--require",
              resolveFixturePath(
                "plugins/global-fixtures/global-setup-teardown-multiple.fixture.cjs",
              ),
              testFile,
            ],
            { cwd: tempDir, budgetMs: watchBudgetMs },
            () => {
              touchFile(testFile);
            },
          ),
          "when fulfilled",
          "to contain once",
          /teardown: this.foo = 3/,
        );
      });
    });
  });
});
