<commit_before>import autoBind from "auto-bind";

import { version } from "../package.json";
import { MigrationError } from "./migration";
import { JsPsychData } from "./modules/data";
import { PluginAPI, createJointPluginAPIObject } from "./modules/plugin-api";
import { ParameterType, universalPluginParameters } from "./modules/plugins";
import * as randomization from "./modules/randomization";
import * as turk from "./modules/turk";
import * as utils from "./modules/utils";
import { TimelineNode } from "./TimelineNode";

function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export class JsPsych {
  extensions = <any>{};
  turk = turk;
  randomization = randomization;
  utils = utils;
  data: JsPsychData;
  pluginAPI: PluginAPI;

  version() {
    return version;
  }

  //
  // private variables
  //

  /**
   * options
   */
  private opts: any = {};

  /**
   * experiment timeline
   */
  private timeline: TimelineNode;
  private timelineDescription: any[];

  // flow control
  private global_trial_index = 0;
  private current_trial: any = {};
  private current_trial_finished = false;

  // target DOM element
  private DOM_container: HTMLElement;
  private DOM_target: HTMLElement;

  /**
   * time that the experiment began
   */
  private exp_start_time;

  /**
   * is the experiment paused?
   */
  private paused = false;
  private waiting = false;

  /**
   * is the page retrieved directly via file:// protocol (true) or hosted on a server (false)?
   */
  private file_protocol = false;

  /**
   * Promise that is resolved when `finishExperiment()` is called
   */
  private finished: Promise<void>;
  private resolveFinishedPromise: () => void;

  /**
   * is the experiment running in `simulate()` mode
   */
  private simulation_mode: "data-only" | "visual" = null;

  /**
   * simulation options passed in via `simulate()`
   */
  private simulation_options;

  // storing a single webaudio context to prevent problems with multiple inits
  // of jsPsych
  webaudio_context: AudioContext = null;

  internal = {
    /**
     * this flag is used to determine whether we are in a scope where
     * jsPsych.timelineVariable() should be executed immediately or
     * whether it should return a function to access the variable later.
     *
     **/
    call_immediate: false,
  };

  constructor(options?) {
    // override default options if user specifies an option
    options = {
      display_element: undefined,
      on_finish: () => {},
      on_trial_start: () => {},
      on_trial_finish: () => {},
      on_data_update: () => {},
      on_interaction_data_update: () => {},
      on_close: () => {},
      use_webaudio: true,
      exclusions: {},
      show_progress_bar: false,
      message_progress_bar: "Completion Progress",
      auto_update_progress_bar: true,
      default_iti: 0,
      minimum_valid_rt: 0,
      experiment_width: null,
      override_safe_mode: false,
      case_sensitive_responses: false,
      extensions: [],
      ...options,
    };
    this.opts = options;

    autoBind(this); // so we can pass JsPsych methods as callbacks and `this` remains the JsPsych instance

    this.webaudio_context =
      typeof window !== "undefined" && typeof window.AudioContext !== "undefined"
        ? new AudioContext()
        : null;

    // detect whether page is running in browser as a local file, and if so, disable web audio and video preloading to prevent CORS issues
    if (
      window.location.protocol == "file:" &&
      (options.override_safe_mode === false || typeof options.override_safe_mode === "undefined")
    ) {
      options.use_webaudio = false;
      this.file_protocol = true;
      console.warn(
        "jsPsych detected that it is running via the file:// protocol and not on a web server. " +
          "To prevent issues with cross-origin requests, Web Audio and video preloading have been disabled. " +
          "If you would like to override this setting, you can set 'override_safe_mode' to 'true' in initJsPsych. " +
          "For more information, see: https://www.jspsych.org/overview/running-experiments"
      );
    }

    // initialize modules
    this.data = new JsPsychData(this);
    this.pluginAPI = createJointPluginAPIObject(this);

    // create instances of extensions
    for (const extension of options.extensions) {
      this.extensions[extension.type.info.name] = new extension.type(this);
    }

    // initialize audio context based on options and browser capabilities
    this.pluginAPI.initAudio();
  }

  /**
   * Starts an experiment using the provided timeline and returns a promise that is resolved when
   * the experiment is finished.
   *
   * @param timeline The timeline to be run
   */
  async run(timeline: any[]) {
    if (typeof timeline === "undefined") {
      console.error("No timeline declared in jsPsych.run. Cannot start experiment.");
    }

    if (timeline.length === 0) {
      console.error(
        "No trials have been added to the timeline (the timeline is an empty array). Cannot start experiment."
      );
    }

    // create experiment timeline
    this.timelineDescription = timeline;
    this.timeline = new TimelineNode(this, { timeline });

    await this.prepareDom();
    await this.checkExclusions(this.opts.exclusions);
    await this.loadExtensions(this.opts.extensions);

    document.documentElement.setAttribute("jspsych", "present");

    this.startExperiment();
    await this.finished;
  }

  async simulate(
    timeline: any[],
    simulation_mode: "data-only" | "visual",
    simulation_options = {}
  ) {
    this.simulation_mode = simulation_mode;
    this.simulation_options = simulation_options;
    await this.run(timeline);
  }

  getProgress() {
    return {
      total_trials: typeof this.timeline === "undefined" ? undefined : this.timeline.length(),
      current_trial_global: this.global_trial_index,
      percent_complete: typeof this.timeline === "undefined" ? 0 : this.timeline.percentComplete(),
    };<commit_msg>fix bug where default simulation mode of data-only didn't work<commit_after>import autoBind from "auto-bind";

import { version } from "../package.json";
import { MigrationError } from "./migration";
import { JsPsychData } from "./modules/data";
import { PluginAPI, createJointPluginAPIObject } from "./modules/plugin-api";
import { ParameterType, universalPluginParameters } from "./modules/plugins";
import * as randomization from "./modules/randomization";
import * as turk from "./modules/turk";
import * as utils from "./modules/utils";
import { TimelineNode } from "./TimelineNode";

function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export class JsPsych {
  extensions = <any>{};
  turk = turk;
  randomization = randomization;
  utils = utils;
  data: JsPsychData;
  pluginAPI: PluginAPI;

  version() {
    return version;
  }

  //
  // private variables
  //

  /**
   * options
   */
  private opts: any = {};

  /**
   * experiment timeline
   */
  private timeline: TimelineNode;
  private timelineDescription: any[];

  // flow control
  private global_trial_index = 0;
  private current_trial: any = {};
  private current_trial_finished = false;

  // target DOM element
  private DOM_container: HTMLElement;
  private DOM_target: HTMLElement;

  /**
   * time that the experiment began
   */
  private exp_start_time;

  /**
   * is the experiment paused?
   */
  private paused = false;
  private waiting = false;

  /**
   * is the page retrieved directly via file:// protocol (true) or hosted on a server (false)?
   */
  private file_protocol = false;

  /**
   * Promise that is resolved when `finishExperiment()` is called
   */
  private finished: Promise<void>;
  private resolveFinishedPromise: () => void;

  /**
   * is the experiment running in `simulate()` mode
   */
  private simulation_mode: "data-only" | "visual" = null;

  /**
   * simulation options passed in via `simulate()`
   */
  private simulation_options;

  // storing a single webaudio context to prevent problems with multiple inits
  // of jsPsych
  webaudio_context: AudioContext = null;

  internal = {
    /**
     * this flag is used to determine whether we are in a scope where
     * jsPsych.timelineVariable() should be executed immediately or
     * whether it should return a function to access the variable later.
     *
     **/
    call_immediate: false,
  };

  constructor(options?) {
    // override default options if user specifies an option
    options = {
      display_element: undefined,
      on_finish: () => {},
      on_trial_start: () => {},
      on_trial_finish: () => {},
      on_data_update: () => {},
      on_interaction_data_update: () => {},
      on_close: () => {},
      use_webaudio: true,
      exclusions: {},
      show_progress_bar: false,
      message_progress_bar: "Completion Progress",
      auto_update_progress_bar: true,
      default_iti: 0,
      minimum_valid_rt: 0,
      experiment_width: null,
      override_safe_mode: false,
      case_sensitive_responses: false,
      extensions: [],
      ...options,
    };
    this.opts = options;

    autoBind(this); // so we can pass JsPsych methods as callbacks and `this` remains the JsPsych instance

    this.webaudio_context =
      typeof window !== "undefined" && typeof window.AudioContext !== "undefined"
        ? new AudioContext()
        : null;

    // detect whether page is running in browser as a local file, and if so, disable web audio and video preloading to prevent CORS issues
    if (
      window.location.protocol == "file:" &&
      (options.override_safe_mode === false || typeof options.override_safe_mode === "undefined")
    ) {
      options.use_webaudio = false;
      this.file_protocol = true;
      console.warn(
        "jsPsych detected that it is running via the file:// protocol and not on a web server. " +
          "To prevent issues with cross-origin requests, Web Audio and video preloading have been disabled. " +
          "If you would like to override this setting, you can set 'override_safe_mode' to 'true' in initJsPsych. " +
          "For more information, see: https://www.jspsych.org/overview/running-experiments"
      );
    }

    // initialize modules
    this.data = new JsPsychData(this);
    this.pluginAPI = createJointPluginAPIObject(this);

    // create instances of extensions
    for (const extension of options.extensions) {
      this.extensions[extension.type.info.name] = new extension.type(this);
    }

    // initialize audio context based on options and browser capabilities
    this.pluginAPI.initAudio();
  }

  /**
   * Starts an experiment using the provided timeline and returns a promise that is resolved when
   * the experiment is finished.
   *
   * @param timeline The timeline to be run
   */
  async run(timeline: any[]) {
    if (typeof timeline === "undefined") {
      console.error("No timeline declared in jsPsych.run. Cannot start experiment.");
    }

    if (timeline.length === 0) {
      console.error(
        "No trials have been added to the timeline (the timeline is an empty array). Cannot start experiment."
      );
    }

    // create experiment timeline
    this.timelineDescription = timeline;
    this.timeline = new TimelineNode(this, { timeline });

    await this.prepareDom();
    await this.checkExclusions(this.opts.exclusions);
    await this.loadExtensions(this.opts.extensions);

    document.documentElement.setAttribute("jspsych", "present");

    this.startExperiment();
    await this.finished;
  }

  async simulate(
    timeline: any[],
    simulation_mode: "data-only" | "visual" = "data-only",
    simulation_options = {}
  ) {
    this.simulation_mode = simulation_mode;
    this.simulation_options = simulation_options;
    await this.run(timeline);
  }

  getProgress() {
    return {
      total_trials: typeof this.timeline === "undefined" ? undefined : this.timeline.length(),
      current_trial_global: this.global_trial_index,
      percent_complete: typeof this.timeline === "undefined" ? 0 : this.timeline.percentComplete(),
    };
{'fraction_non_alphanumeric': 0.11106540518305225, 'fraction_numerical': 0.0008227067050596463, 'mean_word_length': 3.041223404255319, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 23, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3080106', 'n_tokens_mistral': 3542, 'n_tokens_neox': 3360, 'n_words': 1052}