import { Project } from 'fixturify-project'; export { Project } from 'fixturify-project'; /** * A callback used by some of classes to obtain a {@link Project}. * * You are supposed to pass such a callback. In it, you should create and return a {@link Project} * instance. * * @returns A {@link Project} instance, optionally wrapped into a promise. */ type CallbackCreateProject = () => Project | Promise; /** * A callback used to customize an existing {@link Scenario}. In it, you receive a {@link Project} * instance and you can do stuff with it in order to adjust the project's codebase to the specific * needs of a given scenario. * */ type CallbackMutateProject = (project: Project) => void | Promise; /** * A callback for setting up test cases for each scenario. It accepts a {@link Scenario} instance. * * Typically, in a `beforeEach` hook of your test suite, you would run {@link Scenario.prepare} to * emit the correpsonding project to the filesystem and apply all {@link CallbackMutateProject} to * it. */ type CallbackDefineTests = (scenario: Scenario) => void; /** * This is your entry point to this library. Do not use the `new Scenarios()` constructor, instead * use its static methods {@link Scenarios.fromDir} and {@link Scenarios.fromProject}. * * ⚠ `Scenarios` is a separate class and not a plural for `Scenario`. * * `Scenarios` is used to: * * 1. Capture a base scenario by calling {@link Scenarios.fromDir} or {@link Scenarios.fromProject}. * * At this point, the base scenario is represented by a {@link CallbackCreateProject} — a * callback that, when invoked, will create and return a {@link Project} instance representing a * codebase in memory. * * {@link Scenarios.fromDir} captures a codebase into memory from disk. * * {@link Scenarios.fromProject} captures a codebase from a {@link Project} instance. You can * represent a codebase with a JSON-like structure and feed it into * Project.{@link Project."constructor"} to create a {@link Project} instance. * * 2. Create a matrix of test cases by by calling {@link Scenarios.expand}. `expand` acccepts a * record of key-value pairs where keys are names of scenarios and values are * {@link CallbackMutateProject} callbacks. * * The callbacks are not executed just yet, so no {@link Project} or {@link Scenario} * instances are created at this point. * * 3. If you keep the result of {@link Scenarios.expand} in a separate module, you can import that * module into multiple test files. In each of those files, you can: * * * call {@link Scenarios.skip} to remove a scenario from the matrix; * * call {@link Scenarios.only} to select a single scenario from the matrix; * * call {@link Scenarios.map} to append a new {@link CallbackMutateProject} callback to all * sceanarios in the matrix. * * 4. Finally, you call {@link Scenarios.forEachScenario}. It does the following: * * * for each scenario defined in the matrix, it creates a new {@link Scenario} instance; * * for each {@link Scenario} instance, {@link Scenarios.forEachScenario} executes the callback * that you pass into it; * * inside the callback, you receive the {@link Scenario} instance as an argument and use it * to define test cases with a test suite of your choice (QUnit, Mocha, Jest, etc). * * at certain point inside the callback (typically, a `beforeEach` hook of your test suite), * you call Scenario.{@link Scenario.prepare}, which in turn will: * * * instantiate a {@link Project} instance by running the {@link CallbackCreateProject} * callback; * * execute all {@link CallbackMutateProject} callbacks associated with the scenario, in * order to customize the test codebase; * * emit the test codebase to filesystem, either to a temporary directory or to a given * basedir defined in {@link CallbackCreateProject} in {@link Scenarios.fromProject}. * * 5. Now, if you run a file that does `scenarios.forEachScenario` with your test suite, it will * run the tests defined in the callback passed to {@link Scenarios.forEachScenario} for each * scenario in the matrix. * */ declare class Scenarios { private state; /** * Should not be called by hand. Use the {@link Scenarios.fromDir} and * {@link Scenarios.fromProject} static methods instead. */ private constructor(); /** * Instantiates a new `Scenarios` instance, referencing a path to a test codebase on the * filesystem to use as a base scenario. * * Delegates to Project.{@link Project.fromDir}. Note: {@link Project.fromDir} is not executed at * this point. The path is stored as a callback closure which is only executed during * {@link Scenarios.forEachScenario} to produce a {@link Project}, which is then written to disk. * * @param appPath - path to a test codebase on the filesystem to use for base scenario. * @param as - see {@link Project.fromDir}. * @returns a new `Scenarios` instance. */ static fromDir(appPath: string, as?: 'app' | 'lib'): Scenarios; /** * Instantiates a new `Scenarios` instance using a given {@link Project} instance. * * Use this method if you want to define a test codebase in the code of your test file rather than * on a filesystem. See {@link Project} for more info. * * @param callbackCreateProject - a callback that should return a new * {@link Project} instance. This callback is not executed at this point, it is only executed * during {@link Scenarios.forEachScenario}. * @returns a new `Senarios` instance. */ static fromProject(callbackCreateProject: CallbackCreateProject): Scenarios; /** * Defines a number of new scenarios, using the base scenario as a template. * * @param variants - a key-value hash representing derived scenarios: the key is the name of a new * scenario while the value is a {@link CallbackMutateProject} — a callback used to modify a * test codebase to produce a unique scenario. * @returns a new derived `Scenarios` instance containing multiple named * {@link CallbackMutateProject} and a reference to the base scenario (the one that this * method was invoked on). * * Note: this method does not create {@link Scenario} instances. Those instances will only be * created during {@link Senarios.forEachScenario}. */ expand(variants: Record): Scenarios; /** * @param variantName - name of scenario to remove. Note: names of derived scenarios are prepended * with name name of the base scenario: -. * @returns a new `Scenarios` instance with the given scenario removed. */ skip(): Scenarios; skip(variantName: string): Scenarios; /** * @param variantName - name of scenario to keep. Note: names of derived scenarios are prepended * with name name of the base scenario: -. * @returns a new `Scenarios` instance with the given scenario only. Also keeps the base scenario. */ only(variantName: string): Scenarios; /** * Produces a new `Scenarios` instance by deriving new scenarios (aka `variants`) from each of * the previously defined ones. * * If the `Scenarios` instance only contains a base scenario, then adds one derived scenario on * top of it. The name of the derived scenario will be the same as the base one. * * @param name - The name of the new scenario. It will be used to identify the new scenario * variation. * * The name of the new scenario will be composed of the original name and the new name in the * form of: -. * * When applied to a `Scenarios` instance that only has a base scenario defined, then the new * name will be used and the original name will be omitted. * * @param callbackMutateProject - a callback that will be applied to each scenario. It will be * run against a test codebase that will be emitted for this scenario during * {@link Scenarios.forEachScenario}. * * Note that the callback will not replace existing {@link CallbackMutateProject} callbacks, * but rather append to them. * * @returns A new `Scenarios` instance created based on the instanced the method was invoked on. * The new instance will have the new {@link CallbackMutateProject} callback appended to all * scenarios. */ map(name: string, callbackMutateProject: CallbackMutateProject): Scenarios; private iterate; /** * This method is a step that makes `Scenarios` do actual work. It has two purposes: * * 1. It iterates over all defined scenarios (variants). For each scenario, it instantiates a * {@link Scenario} instance and runs Scenario.{@link Scenario.prepare} on it. * 2. It lets you define test cases for each scenario, using a test suite of your choice. You do * it in a callback that you pass to `forEachScenario` as an argument. * Typically, in a `beforeEach` hook of your test suite, you would run * Scenario.{@link Scenario.prepare} to emit the correpsonding project to the filesystem and apply * all {@link CallbackMutateProject} to it. * * @param callbackDefineTests - A callback for setting up test cases for each scenario. */ forEachScenario(callbackDefineTests: CallbackDefineTests): void; } declare global { var scenarioTesterSeenScenarios: Scenario[]; } declare class Scenario { name: string; private callbackCreateProject; private mutators; constructor(name: string, callbackCreateProject: CallbackCreateProject, mutators: CallbackMutateProject[]); prepare(outdir?: string): Promise; } declare class PreparedApp { dir: string; constructor(dir: string); execute(shellCommand: string, opts?: { env?: Record; }): Promise<{ exitCode: number; stderr: string; stdout: string; output: string; }>; } export { type CallbackCreateProject, type CallbackDefineTests, type CallbackMutateProject, PreparedApp, Scenario, Scenarios };