Generative Art: A Wavy Circle

A Constant Radius

nil

class ConstantRadius {
  constructor(length) {
    this.length = length;
  }; // constructor
}; // Constant Radius

A Circulator

class Circulator {
  _to_radians;

  constructor(angle_increment, center_x, center_y, radius, p5) {
    this.angle = 0;
    this.angle_increment = angle_increment;
    this.center_x = center_x;
    this.center_y = center_y;
    this.radius = radius;
    this.p5 = p5;
  }; // constructor

  get to_radians() {
    if (!this._to_radians) {
      this._to_radians = Math.PI/180;
    };
    return this._to_radians;
  }; // to-radians

  get theta() {
    return this.angle * this.to_radians;
  }; // theta

  get theta_opposite() {
    return this.theta + Math.PI;
  }; // theta-opposite

  get x_start() {
    return this.center_x + this.radius.length * Math.cos(this.theta);
  }; // x-start

  get y_start() {
    return this.center_y + this.radius.length * Math.sin(this.theta);
  }; // y-start

  get x_end() {
    return this.center_x + this.radius.length * Math.cos(this.theta_opposite);
  }; // x-end

  get y_end() {
    return this.center_y + this.radius.length * Math.sin(this.theta_opposite);
  }; // y-end

  draw() {
    this.p5.line(this.x_start, this.y_start, this.x_end, this.y_end);
    this.angle += this.angle_increment;
  }; // draw
}; // Circulator
const WIDTH = 500;
const HEIGHT = WIDTH;
const POINT_COLOR = "RoyalBlue";
const CENTER_X = WIDTH/2;
const CENTER_Y = HEIGHT/2;
const RADIUS = WIDTH/2;
function circulator_sketch(p5) {
  let circulator;

  p5.setup = function() {
    p5.createCanvas(WIDTH, HEIGHT);
    p5.background("white");
    p5.stroke(POINT_COLOR);
    p5.fill(POINT_COLOR);
    const radius = new ConstantRadius(RADIUS);
    circulator = new Circulator(1, CENTER_X, CENTER_Y, radius, p5);
  }; // setup

  p5.draw = function() {
    circulator.draw();
  }; // draw
}// circulator_sketch

new p5(circulator_sketch, CIRCULATOR_DIV);

A Fading Circulator

nil

class HSLFader {
  _increment = 1;
  _stroke_color;
  _next_color

  constructor(circulator, p5,  hue=225, saturation=72.7, lightness=56.9) {
    this.circulator = circulator;
    this.p5 = p5;
    this.hue = hue;
    this.saturation = saturation;
    this.lightness = lightness;
  }; // constructor

  get increment() {
    let lightness = this.p5.lightness(this.stroke_color);
    if (lightness >= 100) {
      this._increment = -1;
    } else if (lightness <= 50) {
      this._increment = 1;
    }; // if-else-if

    return this._increment;
  }; // increment

  get stroke_color() {
    if (!this._stroke_color) {
      this.p5.colorMode(this.p5.HSL);
      this._stroke_color = this.p5.color(this.hue,
                                         this.saturation,
                                         this.lightness);
    }; // if
    return this._stroke_color;
  }; // stroke_color

  get next_color() {
    this._stroke_color = this.p5.color(
      this.hue, this.saturation,
      this.p5.lightness(this.stroke_color) + this.increment
    )

    return this.stroke_color;
  }; // next-color

  draw() {
    this.p5.stroke(this.next_color);
    this.circulator.draw();
  }; // draw
}; // CirculaterFader
function hsl_fader_sketch(p5) {
  const WIDTH = 500;
  const HEIGHT = WIDTH;
  const POINT_COLOR = "RoyalBlue";
  const CENTER_X = WIDTH/2;
  const CENTER_Y = HEIGHT/2;
  const RADIUS = WIDTH/2;

  let fader;

  p5.setup = function() {
    p5.createCanvas(WIDTH, HEIGHT);
    p5.background("white");
    p5.stroke(POINT_COLOR);
    p5.fill(POINT_COLOR);

    const radius = new ConstantRadius(RADIUS);
    const circulator = new Circulator(1, CENTER_X, CENTER_Y, radius, p5);

    fader = new HSLFader(circulator, p5);
  }; // setup

  p5.draw = function() {
    fader.draw();
  }; // draw

}; // hsl-fader-sketch

new p5(hsl_fader_sketch, HSL_FADER_DIV);

A Noisy Radius

nil

class NoisyRadius {
  _noise_coordinate = 0;

  constructor(scale, p5, noise_step=0.005) {
    this.scale = scale;
    this.noise_step = noise_step;
    this.p5 = p5;
  }; // constructor

  get noise_coordinate() {
    this._noise_coordinate += this.noise_step;
    return this._noise_coordinate;
  }; // noise_coordinate

  get length() {
    this._length = (this.p5.noise(this.noise_coordinate)
                    * this.scale) + 1;
    return this._length;
  }; // length
}; // NoisyRadius
function noisy_fader_sketch(p5) {
  const WIDTH = 500;
  const HEIGHT = WIDTH;
  const POINT_COLOR = "RoyalBlue";
  const CENTER_X = WIDTH/2;
  const CENTER_Y = HEIGHT/2;
  const RADIUS = WIDTH/2;

  let fader;

  p5.setup = function() {
    p5.createCanvas(WIDTH, HEIGHT);
    p5.background("white");
    p5.stroke(POINT_COLOR);
    p5.fill(POINT_COLOR);

    const radius = new NoisyRadius(RADIUS, p5);
    const circulator = new Circulator(1, CENTER_X, CENTER_Y, radius, p5);

    fader = new HSLFader(circulator, p5);
  }; // setup

  p5.draw = function() {
    fader.draw();
  }; // draw

}; // noisy-fader-sketch

new p5(noisy_fader_sketch, NOISY_FADER_DIV);

A Validator for SliderSettings

The Validator

Validator UML

The Validator class checks the type of a given value and throws an Error if it's not correct. It's meant to validate settings, in particular the SliderSettings.

Class Declaration

The constructor takes the document as an argument to make it testable and also to make explicit where it came from. The class also defines an array emptiness to hold the values that I'll use to check if a variable was set.

Setup JSDOM For Testing

This is the document that I'm passing to the Validator for testing.

const VALID_ID = "validator-id";

const document = new JSDOM(`
<html>
<head></head>
<body>
 <div id=${VALID_ID}></div>
</body>
</html>
`).window.document;

Test And Implement The Validator Class

Feature: Validator

I don't have a "Given" statement in this part of the post even though I'm implementing the Given javascript here because each of the Scenarios after this re-use the same Given but I thought it made sense to go here since it sort of tests the existence of the Validator.

Given("a Validator", function() {
  this.validate = new Validator(document);
});

And here's the class definition that the Given is using.

class Validator {
  emptiness = [null, undefined, NaN];

  constructor(document) {
    this.document = document;
  }

These blocks are the pattern that I'm going to follow for most of the rest of the code:

  1. Feature file fragment.
  2. Test implementation to match the feature file.
  3. Code implementation that's being tested.

Is A Number

Our first method checks that a variable holds a number of some kind.

The Scenarios

Scenario: The expected number is a number.

Given a Validator
When is_a_number is given a number
Then nothing happens.
// Given a Validator

When("is_a_number is given a number", function() {
  this.validate.is_a_number("good-number", faker.number.float());
  this.validate.is_a_number("good-number", 0);
});

Then("nothing happens.", function() {});

This is the case where we get what we wanted.

Note: I added a second check for 0 because I was originally using the falsy check (!(actual)) but it turns out that 0 would be considered false if you do that so I added an explicit check to make sure I wasn't disallowing 0.

Scenario: The expected number isn't a number.

Given a Validator
When an expected number isn't actually a number
Then it throws an Error.
// Given a Validator

When("an expected number isn't actually a number", function() {
  this.bad_call = function() {    
      this.validate.is_a_number("bad-number", faker.lorem.word());
  };
});

Then("it throws an Error.", function() {
  expect(this.bad_call.bind(this)).to.throw(Error);
});

I'm just checking for a string. I suppose there are other checks to be made, but since the Validator is only intended to validate my own code for mistakes, I don't suppose it really needs to be exhaustive.

Scenario: The expected number wasn't assigned.

Given a Validator
When an expected number isn't assigned
Then it throws an Error.
// Given a Validator

When("an expected number isn't assigned", function() {
  this.bad_call = function() {
    this.validate.is_a_number("no-number", null);
  };
});

// Then it throws an error

This isn't explicitly needed, I think, since it falls within "non-number" but I wrote the tests as I made the SliderSettings and sometimes I would get the parameters out of order (I wish javascript had named variables) so I added null checks for the arguments to make it more obvious.

The Method

And here's the implementation.

is_a_number(identifier, actual) {
  if ((!actual && actual !== 0) || isNaN(actual)) {
    throw Error(`"${identifier}" must be a number not "${actual}"`);
  };
}; // is_a_number

The first condition checks that the number isn't 'falsy', but in javascript 0 is considered falsy so to allow zeros I added the check that it's not 0 if it's falsy. The conditional also checks if it is javascript's idea of a NaN using the global isNaN. This function coerces values to numbers (e.g. the string "120" is not Nan) so I originally used Number.isNaN, since the documentation says that it doesn't coerce values, but that turns out to mean that it just returns false without coercing the string… I suppose there's a reason for this, particularly since NaN is meant for numeric data types, so a string is "not a number" but it can't be NaN, but whatever the reasion, it's something to remember, although it seems odd that, in being more strict, Number.isNaN ends up returning the same value as the global version.

Is Set

This is for the cases where I have no particular type in the mind but a variable does need to be set to something.

Scenarios

Scenario: The variable has a value set.

Given a Validator
When is_set is given a variable that's set
Then nothing happens.
// Given a Validator

When("is_set is given a variable that's set", function() {
  this.validate.is_set("set-variable", faker.lorem.word());
  this.validate.is_set("set-variable", 0);
  this.validate.is_set("set-variable", false);
});

// Then nothing happens.

Given the broad view of what I'm saying is_set should check for it'd be hard to check all the possibilities so this mostly checks that I didn't use a falsy check or something like that which would create false negatives.

Scenario: The variable is empty.

Given a Validator
When is_set is given an empty variable
Then it throws an Error.
// Given a Validator

When("is_set is given an empty variable", function() {
  this.bad_call = function() {
    this.validate.is_set(null);
  };
});

// Then it throws an Error.

Checking for null should be the most common case, since I'm going to use this to validate an object and make sure it's attributes were all set.

Given a Validator
When is_set is given an undefined variable
Then it throws an Error.
// Given a Validator

When("is_set is given an undefined variable", function() {
  this.bad_call = function() {
    this.validate.is_set(undefined);
  };
});

// Then it throws an Error.

I wouldn't think this would be something that needs to be checked, but since javascript just returns undefined instead or raising an error if you misspell a variable name, I guess it's useful.

The Method

This checks if the value is in whatever is in the emptiness array, which as of now has:

  • null
  • undefined
  • NaN

I'm not sure about that last one. I think I was trying to use all the falsy values that weren't likely to be actual values (like 0, false), but now you can't use infinity either. Not that I can think of a case that I would, but maybe that'll have to be taken out later.

is_set(identifier, actual) {
  if (this.emptiness.includes(actual)) {
    throw Error(`"${identifier} must be set, not "${actual}"`);
  };
}; //is_set

Is An Integer

The Scenarios

Scenario: The variable has an integer

Given a Validator
When is_an_integer is given a variable with an integer
Then nothing happens.
// Given a Validator

When("is_an_integer is given a variable with an integer", function() {
  this.validate.is_an_integer("is-integer", faker.number.int());
  this.validate.is_an_integer("is-integer", 1.0);
});

// Then nothing happens

Our happy-path case. The second check in the When is there to make it clearer that even though 1.0 smells like a float, Number.isInteger treats it like an integer.

Scenario: The variable has a string

Given a Validator
When is_an_integer is given a string
Then it throws an Error.
// Given a Validator

When("is_an_integer is given a string", function() {
  this.bad_call = function() {
    this.validate.is_an_integer("not-integer", `${faker.number.int()}`);
  };
});

// Then it throws an Error.

I think this is the most likely error - it was passed a string. Interestingly, like the Number.isNaN function, the Number.isInteger function that I'm using also doesn't coerce strings so while "5" isn't not NaN, it also isn't an integer.

Scenario: "is_an_integer" is given a float.

Given a Validator
When is_an_integer is given a float
Then it throws an Error.
// Given a Validator

When("is_an_integer is given a float", function() {
  this.bad_call = function() {
    this.validator.is_an_integer("float-not-integer", 5.5);
  };
});

// Then it throws an Error.

Since I showed above that 5.0 is considered an integer I felt obliged to make sure that other floats aren't considered integers.

Scenario: The integer variable wasn't set.

Given a Validator
When an expected integer wasn't set
Then it throws an Error.
// Given a Validator

When("an expected integer wasn't set", function() {
  this.bad_call = function() {
      this.validate.is_an_integer("no-integer", null);
  };
});

// Then it throws an Error.

The Method

This is, oddly, the only built-in that I could find that does type checks (but I didn't look that hard, and I was using DuckDuckGo so I might have found something using a different search engine).

is_an_integer(identifier, actual) {
  if (!Number.isInteger(actual)) {
    throw Error(`"${identifier}" must be an integer, not ${actual}`);
  };
}; // is_an_integer

Is An Element's ID

This is what really started it all. I had some mysterious errors drawing a spiral which turned out to be because I had changed a div ID in the HTML but not in the javascript. So this checks to see if there really an element with the ID. It doesn't check if it's the right ID, but I don't know that there's a simple way to do that anyway.

The Scenarios

Scenario: A valid ID is given.

Given a Validator
When is_an_element_id is given a valid element ID
Then nothing happens.
// Given a Validator

When("is_an_element_id is given a valid element ID", function() {
  this.validate.is_an_element_id("good-id", VALID_ID);
});

// Then nothing happens.

Since I'm using JSDOM I needed to use a real ID to check if it was valid, not a random string.

Scenario: An invalid ID is given.

Given a Validator
When is_an_element is given an invalid element ID
Then it throws an Error.
// Given a Validator

When("is_an_element is given an invalid element ID", function() {
  this.bad_call = function() {
    this.validate.is_an_element_id("bad-id", VALID_ID + "invalid");
  };
});

// Then it throws an Error.

Although I suppose the odds of a random string matching my div ID is pretty low, I thought that mangling the ID would be a better guaranty that it won't match than using faker to generate a string.

The Method

This relies on the built-in document.getElementById method (well, built-in when there's a browser).

is_an_element_id(identifier, actual) {
  if (this.document.getElementById(actual) === null) {
    throw Error(`"${identifier}" isn't a valid ID - "${actual}"`);
  };
}; // is_an_id

Links

Javascript

The Great Slidini

Slidini

nil

The Class Declaration

The Slidini class is going to create two HTML elements - a Slider and a label (what I call a caption) for the slider.

class Slidini {
  _slider = null;
  _caption = null;

The Constructor

Feature: A Slidini class to hold a slider and its caption.
constructor(slider_settings, caption_settings, p5) {
  this.slider_settings = slider_settings;
  this.caption_settings = caption_settings;
  this.p5 = p5;
} // constructor

The slider_settings and caption_settings should be instances of the SliderSettings and CaptionSettings classes (see the Slider and Caption Settings post) and the p5 object should be a p5.js instance.

The Slider

This creates the slider and the caption. I decided to muddy them together like this in order to create the callback to update the caption whenever the slider is updated, and to add the initial caption once the slider is created.

get slider() {
  if (this._slider === null) {
    // create the slider
    this._slider = this.p5.createSlider(
      this.slider_settings.min,
      this.slider_settings.max,
      this.slider_settings.default_value,
      this.slider_settings.step_size,
    );

    // attach it to the div tag
    this._slider.parent(this.slider_settings.slider_div);

    // set the callback to change label on update
    this._slider.input(() => this.update_caption());

    // add the label to the slider
    this.update_caption();
  }
  return this._slider;
}

The Caption

This is the caption for the slider. It expects there to be a div that it can stick the caption into.

get caption() {
  if (this._caption === null) {
    this._caption = this.p5.select(
      this.caption_settings.div_selector);
  }
  return this._caption;
}

The Caption Updater

This sets the caption to the current value of the slider. It's used both to initialize the caption and as a callback to update the caption whenever the slider's value changes.

update_caption() {
  this.caption.html(
    `${this.caption_settings.label}: ` +
      `${this.slider.value().toFixed(this.caption_settings.precision)}`);
} // update_caption

Links

Reference

Slider and Caption Settings

The Sliders Settings

nil

The SliderSettings class holds the values for the Slidini class and optionally validates the values it's been given.

The Scenario

Since there's only one method to call and it defers everything to the Validator I'm going to have one Scenario to test, but to try and make it easier to read I'm going to break up the Then-And statements within it, but I'm not going to break up the check_rep method itself so I'm not going to show the implementation under each test it satisfies, but just show the class definition in entirety after all the tests.

The Testing

First we need to import some javascript. Even though I'm faking all the methods I'm going to use on the Validator class I used the real definition because I was hoping to figure out how to get sinon to copy all the methods automatically, but I didn't see anything indicating it can, so maybe next time I'll just make a fake object instead.

import { expect } from "chai";
import { faker } from "@faker-js/faker";
import { Given, When, Then } from "@cucumber/cucumber";
import { fake, replace } from "sinon";
import { SliderSettings } from "../../../../files/javascript/slider.js";
import { Validator } from "../../../../files/javascript/validator.js";

Since the Validator's methods get called more than once I need to be able to know what (zero-based) index each call is - e.g. checking default_value is the third Validator.is_a_number call that the SliderSettings makes, so to retrieve the object to check that the call went as expected I need to get the sinon call object at index 2. I'm making the IS object below to hold the indices to get the calls for each property… it'll make more sense later.

const IS = {
  NUMBER: { min: 0,
            max: 1,
            default_value : 2,
            step_size: 3,               
          },
  ELEMENT: {
    slider_div: 0,
  }
};

const METHODS = ["is_a_number", "is_set", "is_an_integer", "is_an_element_id"];

The METHODS array holds the names of all of the Validator's methods that check_rep uses so that I can replace the Validator's methods in a loop instead of doing it separately for each one.

Setup The Slider Settings

Now I'll build the SliderSettings with the faked Validator methods in the cucumber Given function.

Feature: Slider Settings

Scenario: check_rep is called.

Given a Slider Settings

Since all the methods are going to be faked, I don't need a mock document the way I did for the Validator tests.

Given("a Slider Settings", function() {
  this.validator = new Validator({});

Now that I have a Validator instance, I can replace all the methods to test with fakes.

for (const method of METHODS) {
    replace(this.validator, method,
          fake.returns(null));    
}

Next, I'll fake the arguments passed to the SliderSettings object and store them in the World this so that I can check that they were passed to the validator as expected in the tests.

this.min = faker.number.float();
this.max = faker.number.float();
this.default_value = faker.number.float();
this.step_size = faker.number.float();
this.slider_div = faker.lorem.word();

Finally, I can create the SliderSettings to test.

this.settings = new SliderSettings(this.min,
                                   this.max,
                                   this.default_value,
                                   this.step_size,
                                   this.slider_div,
                                   this.validator);
});

Calling Check Rep

This is the only call to SliderSettings I make.

When check_rep is called
When("check_rep is called", function() {
  this.settings.check_rep();
});

Now the rest of the tests check all the calls to the Validator that the check_rep method made.

Min Check

The first property that check_rep validates is the min.

Then it checked the min
// Given a Slider Settings
// When check_rep is called

Then("it checked the min", function() {
  expect(this.validator.is_a_number.getCall(IS.NUMBER.min).calledWith(
    "min", this.min
  )).to.be.true;  
});

this.validator.is_a_number is a faked method which allows us to check the arguments passed to it by getting the call object using getCall and checking the arguments with calledWith. In this case checking min is the first call to is_a_number so I'm passing 0 to getCall, retrieving it from the IS object I created earlier (using IS.NUMBER.min).

I'm not crazy about the need to pass in strings, but since they always match the variable name I guess it's easy enough to see any typos.

The rest of the checks are pretty much the same thing but with different variables so I'll stop the commentary for a while.

Max Check

And it checked the max
Then("it checked the max", function() {
  expect(this.validator.is_a_number.getCall(IS.NUMBER.max).calledWith(
    "max", this.max
  )).to.be.true;
});

Default Value

And it checked the default_value
Then("it checked the default_value", function() {
  expect(this.validator.is_a_number.getCall(IS.NUMBER.default_value).calledWith(
    "default_value", this.default_value
  )).to.be.true;
});

Step Size

And it checked the step_size
Then("it checked the step_size", function() {
  expect(this.validator.is_a_number.getCall(IS.NUMBER.step_size).calledWith(
    "step_size", this.step_size
  )).to.be.true;
});

Slider Div

And it checked the slider_div
Then("it checked the slider_div", function() {
  expect(this.validator.is_an_element_id.getCall(IS.ELEMENT.slider_div).calledWith(
    "slider_div", this.slider_div
  )).to.be.true;
});

The Slider Settings Implementation

Now that we have the tests, I'll implement the slider settings.

nil

The SliderSettings holds the settings to build Slidini, the Slider and Caption holder. It really could be done with a plain object (which is what it was) but I decided to add a validator to make sure that I was getting all the parameters right.

class SliderSettings {
  constructor(min, max, default_value, step_size,
              slider_div,
              validator) {
    this.min = min;
    this.max = max;
    this.default_value = default_value;
    this.step_size = step_size;
    this.slider_div = slider_div;
    this.confirm = validator;
  }; // constructor

  check_rep(){
    this.confirm.is_a_number("min", this.min);
    this.confirm.is_a_number("max", this.max);
    this.confirm.is_a_number("default_value", this.default_value);
    this.confirm.is_a_number("step_size", this.step_size);
    this.confirm.is_an_element_id("slider_div", this.slider_div);
  }; // check_rep
}; // SliderSettings

The Caption Settings

The Caption Settings are pretty much just like the Slider Settings except that they are meant for the label that lets the user know what the current slider's value is. I used to have everything in the SliderSettings but it wasn't obvious what belonged to which so I broke them apart.

The Testing

This is pretty much exactly the same as the testing for the SliderSettings so I won't have a whole lot to add to it.

Imports

import { expect } from "chai";
import { faker } from "@faker-js/faker";
import { Given, When, Then } from "@cucumber/cucumber";
import { fake, replace } from "sinon";

import { CaptionSettings } from "../../../../files/javascript/slider.js";
import { Validator } from "../../../../files/javascript/validator.js";

Call Object Indices

const CAPTION_IS = {
  SET: {
    label: 0
  },
  INTEGER: {
    precision: 0
  },
  ELEMENT: {
    caption_div: 0
  }
};

const METHODS = ["is_a_number", "is_set", "is_an_integer", "is_an_element_id"];

The Feature

Feature: Settings for a caption/label.

Scenario: The CaptionSettings is built.
Given a CaptionSettings
When the properties are checked
Then they are the expected properties.

Setting up the Caption Settings in "Given"

Given("a CaptionSettings", function() {

These are the three properties that the Slidini class is going to need to set up the label.

this.label = faker.lorem.words();
this.precision = faker.number.int();
this.caption_div = faker.lorem.word();
this.div_id_selector = "#" + this.caption_div;

Once again I'm replacing the Validator methods so I can check the calls and as a side-effect the document won't get used so I don't need JSDOM.

this.validator = new Validator({});

for (const method of METHODS) {
    replace(this.validator, method,
          fake.returns(null));    
}

And finally I'll build our software to test.

this.caption_settings = new CaptionSettings(this.label,
                                            this.precision,
                                            this.caption_div,
                                            this.validator);

Putting the values to check into variables seems like an unnecessary step, since you could test and retrieve the properties at the same time, but I like the use of When and it makes the lines in the Then block a little shorter.

When("the properties are checked", function() {
  this.actual_label = this.caption_settings.label;
  this.actual_precision = this.caption_settings.precision;
  this.actual_caption_div = this.caption_settings.caption_div;
});
Then("they are the expected properties.", function() {
  expect(this.actual_label).to.equal(this.label);
  expect(this.actual_precision).to.equal(this.precision);
  expect(this.actual_caption_div).to.equal(this.caption_div);
});

Check Rep

Scenario: check_rep is called.

Given a CaptionSettings
When CaptionSettings.check_rep is called

Oddly, when I just said "When check_rep is called" instead of "When CaptionSettings.check_rep is called" cucumber ended up using the function I made for the SliderSettings tests that had the same When string. For some reason it lets them pollute each other's tests, even though they have separate feature and step files. I suppose this could make it easier to re-use test-functions, but it makes it kind of dangerous since you have to make sure that everything has a unique title.

Or maybe there's something I'm missing…

When("CaptionSettings.check_rep is called", function() {
  this.caption_settings.check_rep();
});

Did It Validate the Label?

Then it checks the label
Then("it checks the label", function() {
  expect(this.validator.is_set.getCall(CAPTION_IS.SET.label).calledWith(
    "label", this.label
  )).to.be.true;  
});

Did It Validate the Precision?

And it checks the precision
Then("it checks the precision", function() {
  expect(this.validator.is_an_integer.getCall(CAPTION_IS.INTEGER.precision).calledWith(
    "precision", this.precision
  )).to.be.true;  
});

Did It Validate the Div ID?

And it checks the caption div ID.
Then("it checks the caption div ID.", function() {
  expect(this.validator.is_an_element_id.getCall(
    CAPTION_IS.ELEMENT.caption_div).calledWith(
      "caption_div", this.caption_div
    )).to.be.true;  
});

Does it add a pound sign to the DIV ID?

The p5.select method uses CSS selectors so it needs you to put a # sign in front of the DIV ID to tell it that it's an ID.

Scenario: The caption DIV selector is set up

Given a CaptionSettings
When the caption DIV ID selector is retrieved
Then the caption DIV selector has the pound sign.
When("the caption DIV ID selector is retrieved", function() {
  this.actual_div_id_selector = this.caption_settings.div_selector;
});

Then("the caption DIV selector has the pound sign.", function() {
  expect(this.actual_div_id_selector).to.equal(this.div_id_selector);
});

The CaptionSettings Implementation

So here's where I implemennt the class. The label property is a string that gets inserted into the string that's displayed on the Label element. Maybe I should have called it something else… The precision property is used to decide how many decimal places to show in the Label, and the caption_div is the ID of the element where I'm going to stick the Label.

class CaptionSettings {
  _div_selector = null;

  constructor(label, precision, caption_div, validator) {
    this.label = label;
    this.precision = precision;
    this.caption_div = caption_div;
    this.validator = validator;
  };

  get div_selector(){
    if (this._div_selector === null) {
      this._div_selector = "#" + this.caption_div;
    }
    return this._div_selector;
  };

  check_rep() {
    this.validator.is_set("label", this.label);
    this.validator.is_an_integer("precision", this.precision);
    this.validator.is_an_element_id("caption_div", this.caption_div);
 };
}; // CaptionSettings

Links

References

Cucumber, Chai, and JSDOM

What Is This About?

The Earlier Problem

In my code from my spiral post I used document.getElementById as a check to make sure that I was passing in a valid div ID to p5.js and I decided to add some testing to make sure my validator was working as I expected. The problem was that when you run tests in node.js the document object doesn't exist. One way to get around this might be to use Selenium or some other browser-tester but that seemed like overkill.

My first idea was that I would just mock the document object using sinon since this is a fairly easy thing to do in python (maybe not mocking document, specifically, since that is a javascript thing, but mocking objects in a module is fairly easy) but I simply couldn't find anything that seemed to indicate that this was possible, only posts saying that sinon doesn't work that way.

So then I stumbled upon jsdom, which appeared to be what I wanted to mock the document. The problem was that it's more of a browser simulator not a means to replace objects so I still had to figure out a way to replace the document that my class was expecting with the jsdom document. In the other post where I first used jsdom I followed the pattern shown in this github repository where you stick the jsdom document into the global variable which makes it appear to be a global variable just like it is when you run the code in a browser.

global.document = dom.window.document;

Where dom is an instance of jsdom setup with some HTML to use for the testing. Amazingly this did seem to work…

The Newer Problem

Then I got to this post where I wanted to document getting jsdom to work and, even though I was using a simpler version of the previous post, I found that the tests were failing mysteriously (pretty much everything with javascript is mysterious to me). With a lot of troubleshooting, I managed to find out that instead of using the dom I was creating here it was still using the same one from the other testing. Except sometimes it was using the dom I defined here and these tests passed but then the other tests failed. I guess global means global in the truer sense, not just for the scope of a single set of tests, and there's some kind of race going on between the different pieces of code that are trying to set and use this global document. Oy.

So, then I went looking at the jsdom documentation (which is pretty much just a readme and all the "issues" people have posted), but there was one page on their wiki titled Don't Stuff jsdom Globals Onto the Node Global, which, I guess, means that I shouldn't have done what I did and all those Stack Overflow answers say to do. The page scolding all us silly people for using global did have a few examples of how they thought you should do it instead, so then I tried their solution of adding the javascript for my class to the jsdom object instead of using it in the cucumber.js test code. That way my class would have access to the document in their global space.

Easy-peasy. Well… this produced more mysterious failures except now it was in a different place. After running their examples in the Node REPL unsuccessfully I found this "issue" where it's explained that they don't support the <script type="module"> parameter (so you can't use ES6 imports like I do). Okay, so then I dumped the class definition to a string and added it directly, but no matter what I couldn't get jsdom to interpret any class I put in, although functions did work, so I came to the conclusion that they don't support javascript classes either.

I couldn't find anything specific about not supporting classes, but trying to search using terms like class and document brings up so much irrelevant stuff that it's maddening to even look for anything so there might have been some skimming fatigue that blinded me to any documents about it, if they existed.

But then, while fiddling with the Node REPL I found that defining the document before instantiating my class made it work, so I thought, okay, why am I trying so hard to do all this patching when I can just create the document object in my tests and then create the object under test? Well, the answer to that is - "because it doesn't work". For some reason the document object existed before and after creating my test object but it was always undefined within the class method I was testing. Mysterious.

In the end I ended up doing an ugly workaround which didn't really even require using jsdom, although I guess using it at least validates that I'm using the right method name… Anyway, here it is.

The Feature File

This is a Cucumber.js test so I'll create a simple feature file for a class that retrieves a document element and test the case where the ID is correct and the one where it is incorrect.

Feature: An Element Getter

Scenario: A Valid ID

Given an ElementGetter with the correct ID
When the element is retrieved
Then it is the expected element.

Scenario: The wrong ID.

Given an ElementGetter with the wrong ID
When the element is retrieved using the wrong ID
Then it throws an error.

The Software Under Test

Now the implementation. This is kind of a useless class, but this was supposed to be about how to get jsdom working so I can test code expecting a document.

The Class Declaration

This is the class I'm going to test.

nil

class ElementGetter {
  #element;

  constructor(element_id, document) {
    this.element_id = element_id;
    this.document = document;
  };

The constructor shows the main change I made to get it working - instead of using a global document I added it as an argument. I went through all that rigamarole trying to avoid this since it seemed like I was changing code just to test it, but now that I think about it, it's what I'd've done in python anyway, since I kind of don't like these "magic" objects that show up without being created or imported like they do in javascript.

The Get Element Method

I made a getter for the retrieved element. It probably would have been easier in this case if I didn't store it, since I could test both when the ID is correct and when it isn't with the same object, just by changing the this.element_id value, but it's a pattern I often use and it gave me the chance to test out javascript's Private Class Fields syntax. To be quite honest, I think using the pound sign (#) is kind of ugly - I prefer the underscore, and Pygments draws red boxes around the #- but at least I know about it.

The main value in using a getter here is that it can check that the element exists, since an invalid ID passed to getElementById will just return a null object rather thhan throw in error.

get element() {
  if (!this.#element) {
    this.#element = this.document.getElementById(this.element_id);
    if (!this.#element) {
      throw Error(`Unable to pull the element with ID '${this.element_id}'`);
    };
  };
  return this.#element;
};

The Step File

And now the test code.

Setting Up

Import the Test Libraries

import { expect } from "chai";
import { Given, When, Then } from "@cucumber/cucumber";
import { JSDOM } from "jsdom";

These are the libraries that I installed to support testing. Using jsdom instead of creating a mock was convenient, but I might have to watch what the overhead is if I make a lot of tests that use it.

Import the Software Under Test

Now I'll import the ElementGetter class that I defined above. It occurs to me that for a case like this where I don't actually use the code for anything other than testing I could have put it next to the tests, but I guess this is a better dress rehearsal for really using code in a post.

Note the extra step up the path (../) because this time I followed the cucumber example and put the steps in a folder named steps instead of in a file named steps.js, which might make it easier to organize in the future if I have more to test, but makes relative paths that much more painful.

import { ElementGetter } from "../../../../files/posts/cucumber-chai-and-jsdom/puller.js";

Setup JSDOM

Here's where I create the jsdom object with a div that the ElementGetter can get. I'm passing in the whole HTML string but in the documentation they sometimes just pass in the body.

const EXPECTED_ID = "expected-div";
const document = (new JSDOM(`<html>
<head></head>
<body>

  <div id='${EXPECTED_ID}'></div>

</body></html>`)).window.document;

The Tests

The Right ID

This is the first scenario where I expect the ElementGetter to successfully find the element. There's not a lot to test here, other than it doesn't crash.

Given("an ElementGetter with the correct ID", function() {
  this.puller = new ElementGetter(EXPECTED_ID, document);
});
When("the element is retrieved", function() {
  this.actual_element = this.puller.element;
});
Then("it is the expected element.", function() {
  expect(this.actual_element.id).to.equal(EXPECTED_ID);
});

The Wrong ID

This is the more interesting case where we give the ElementGetter an ID that doesn't match any element in the page.

Given("an ElementGetter with the wrong ID", function() {
  this.puller = new ElementGetter(EXPECTED_ID + "abc", document);
  });

Since I made a getter to retrieve the element, you can't pass it directly to chai to test - trying to pass this.puller.element to chai will trigger the error before chai gets it - so instead I'm using something I learned working with pytest. I'm creating a function that will retrieve the element and then passing the function to chai to test that it raises an error.

When("the element is retrieved using the wrong ID", function() {
  this.bad_call = function(){
    this.puller.element
  }
});
Then("it throws an error.", function() {
  expect(this.bad_call).to.throw(Error);
});

What Have We Learned?

I suppose the biggest lesson is that I shouldn't have tried so hard to fake the document object as a magic global object the way it normally is used and instead just gone for an explicit argument that gets passed to the class (or function) that needs it (which sort of follows the Dependency Injection Pattern). I also learned that jsdom is interesting but behind the ECMA standard, as were some of the other libraries I ran into in trying to solve different parts of this problem, so I have to either decide to not use ECMA 6 or not rely so much on these other libraries that don't use the current standards.

Generative Art: Concentric Circles

Introduction

This is a sketch that extends the Generative Art Circles post (slightly) to make concentric circles. I was going to make a spiral but realized after I wrote out the code that I had actually made concentric circles - so it's sort of a half-step from the circle to the spiral.

The Class

The ConcentricCircles class keeps track of the parameters for the circles and draws them. Here I'm declaring the class and some fields to store the parameters.

class ConcentricCircles {
  // geometry
  degrees_in_a_circle = 360;
  to_radians = (2 * Math.PI)/ this.degrees_in_a_circle;

  // the starting values for the circles
  radius = 5;
  _step = 5;

  // the center of our sketch (and the circles)
  center_x;
  center_y;

  // the size of the circle to draw  the circles
  point_diameter = 1;

  constructor(p5, center_x, center_y, maximum_radius){
    this.p5 = p5;
    this.center_x = center_x;
    this.center_y = center_y;
    this.maximum_radius = maximum_radius;
  } // constructor

The constructor takes the p5.js object and the coordinates for the center of the circles (center_x and center_y) as well as the maximum_radius - the value at which the circles have hit their limit and should turn around. This is presumably half the width of the canvas, but since the ConcentricCircles class isn't creating the canvas I thought it should be the code that creates the sketch that decides what the limit is.

The Step

The step is the amount the radius increases between each circle. I put it in a getter so that it can check if the circles are at the limits of the expected maximum (or minimum) size and thus should change direction (shrink instead of grow or vice-versa).

get step() {
  if (this.radius > (this.maximum_radius - this._step) || this.radius <= 0) {
      this._step *= -1;
    }
  return this._step
}

The Draw

The draw method is what the sketch function calls to tell the ConcentricCircles to draw a circle. This is similar to the sketch that drew a single circle except that the radius gets incremented at after the circle is drawn.

draw() {
  let radians, x, y;

  for (let angle = 0; angle < this.degrees_in_a_circle; angle += 1){
      radians = angle * this.to_radians;
      x = this.center_x + this.radius * Math.cos(radians);
      y = this.center_y + this.radius * Math.sin(radians);
      this.p5.circle(x, y, this.point_diameter);
  }

  this.radius += this.step;
}

The Sketch Function

This is the function that gets passed to p5 to execute the setup and draw methods.

Concentric Circles

After declaring the function and some constants for the canvas, it creates an instance of the ConcentricCircles class.

function concentric_circles(p5){
  // the size of the canvas and the color of the circles
  const WIDTH = 500;
  const HEIGHT = WIDTH;
  const POINT_COLOR = "RoyalBlue";

  const circles = new ConcentricCircles(p5, WIDTH/2, HEIGHT/2, WIDTH/2);

Set Up

The setup method doesn't do anything fancy, although I did have to set the frameRate to a slower speed otherwise I couldn't see the circles being animated.

p5.setup = function(){
  p5.createCanvas(WIDTH, HEIGHT);
  p5.background("white");
  p5.stroke(POINT_COLOR);
  p5.fill(POINT_COLOR);
  p5.frameRate(10);
} // end setup

Draw

The draw method defers to the ConcentricCircles.draw method to do the actual drawing of the circles, but I added a light white overlay so that the circles fade out and you can see the animation.

p5.draw = function() {
  circles.draw();
  p5.background(255, 75);
}// end draw

Passing The Sketch to p5.js

That's pretty much it for the sketch, the last thing to do is just pass the concentric_circles function to p5 along with the id for the div where the sketch should go (which I defined but don't show in the post).

new p5(concentric_circles, CONCENTRIC_CIRCLES_DIV);

The End

And that's it for drawing concentric circles, now on to spirals.

Generative Art: Spiral

The Div IDs

To specify where the parts of the sketch go I added some div tags to the HTML so I'm going to create some javascript constants with the div IDs to make it easier to keep track of them.

The Sketch Div

This is the ID for the div where the main sketch will go, it gets passed to the p5 constructor, along with the sketch definition, to create the the P5 instance.

const SPIRAL_DIV = "spiral-0a168ba9";

The Slider Divs

I'm going to add some sliders to make it easier to adjust some of the parameters and see how that affects the sketch. These are the IDs of the div tags where I'm going to put the sliders to change some of the sketch values. The angle and radius sliders will set how much the angle and radius will change as the circle is drawn. If, for example, the angle slider is set to 5, then every point that's added will be rotated five degrees from the previous point, and if the radius is set to 5, then the radius will grow by 5 every time a point is added.

The circle slider is a little different in that it sets the diameter for the circles that I'm drawing to create the spiral, so it's just an aesthetic setting.

const SPIRAL_ANGLE_SLIDER = "angle-slider-0a168ba9";
const SPIRAL_RADIUS_SLIDER = "radius-slider-0a168ba9";
const SPIRAL_CIRCLE_SLIDER = "circle-slider-0a168ba9";

Text Divs

I also created some div tags that I'll put some text into to show the current value of each of the sliders.

const SPIRAL_ANGLE_TEXT = "angle-text-0a168ba9";
const SPIRAL_RADIUS_TEXT = "radius-text-0a168ba9";
const SPIRAL_CIRCLE_TEXT = "circle-text-0a168ba9";

The Slider Settings

The Validator

This is used by the settings classes to try and see if I'm passing in valid arguments.

const VALIDATOR = new Validator(document);

Angle Slider

The values used to create the angle-increment slider.

const ANGLE_SLIDER = new SliderSettings(
  0,
  40,
  5,
  0,
  SPIRAL_ANGLE_SLIDER,
  VALIDATOR
);

const ANGLE_CAPTION = new CaptionSettings(
  "Angle Increment",
  2,
  SPIRAL_ANGLE_TEXT,
  VALIDATOR
);

ANGLE_SLIDER.check_rep();
ANGLE_CAPTION.check_rep();

Radius Slider

The values used to create the radius increment slider.

const RADIUS_SLIDER = new SliderSettings(
  0,
  20,
  1,
  0,
  SPIRAL_RADIUS_SLIDER,
  VALIDATOR
);

const RADIUS_CAPTION = new CaptionSettings(
  "Radius Increment",
  2,
  SPIRAL_RADIUS_TEXT,
  VALIDATOR
);

RADIUS_SLIDER.check_rep();
RADIUS_CAPTION.check_rep();

Circle Slider

The values used to create the circle diameter slider.

const CIRCLE_SLIDER = new SliderSettings(
  1,
  100,
  1,
  0,
  SPIRAL_CIRCLE_SLIDER,
  VALIDATOR
);

const CIRCLE_CAPTION = new CaptionSettings(
  "Point Diameter",
  2,
  SPIRAL_CIRCLE_TEXT,
  VALIDATOR
);

CIRCLE_SLIDER.check_rep();
CIRCLE_CAPTION.check_rep();

The Spiralizer

Class Declaration

class Spiralizer {
  // geometry
  degrees_in_a_circle = 360;
  to_radians = (2 * Math.PI)/ this.degrees_in_a_circle;

  // the starting values for the circles
  radius = 1;
  angle = 0;

  // the center of our sketch (and the circles)
  center_x;
  center_y;

The Constructor

constructor(p5, center_x, center_y, maximum_radius,
            angle_slider, radius_slider, circle_slider){
  this.p5 = p5;
  this.center_x = center_x;
  this.center_y = center_y;
  this.maximum_radius = maximum_radius;

  // the amount to move the points on the circle as they're drawn
  this.angle_increment = angle_slider;
  this.radius_increment = radius_slider;

  // the size of the circle to draw  the circles
  this.point_diameter = circle_slider;
} // constructor

The Draw Method

draw() {
  let radians, x, y;

  radians = this.angle * this.to_radians;
  x = this.center_x + this.radius * Math.cos(radians);
  y = this.center_y + this.radius * Math.sin(radians);
  this.p5.circle(x, y, this.point_diameter.value());


  this.radius += this.radius_increment.value();
  this.angle += this.angle_increment.value();

  if (this.radius >= this.maximum_radius) {
    this.radius = this.radius_increment.value();
  }
} // end draw

Reset

reset() {
  this.radius = this.radius_increment.value();
  this.angle = 0;
} // end reset

The Spiral Sketch

This is going to be the sketch that we pass to the P5 constructor to create the animation.

Function Declaration

function spiral_sketch(p5) {
  // the size of the canvas and the color of the circles
  const WIDTH = 500;
  const HEIGHT = WIDTH;
  const POINT_COLOR = "RoyalBlue";

  let spiralizer;
  let angle_slider;
  let radius_slider;
  let circle_slider;

Setup

Setup The Canvas and Drawing Settings

p5.setup = function(){
  p5.createCanvas(WIDTH, HEIGHT);
  p5.background("white");
  p5.stroke(POINT_COLOR);
  p5.fill(POINT_COLOR);

Create The Sliders

angle_slider = new Slidini(ANGLE_SLIDER, ANGLE_CAPTION, p5);  
radius_slider = new Slidini(RADIUS_SLIDER, RADIUS_CAPTION, p5);
circle_slider = new Slidini(CIRCLE_SLIDER, CIRCLE_CAPTION, p5);

Create the Spiralizer and End the Setup

spiralizer = new Spiralizer(p5, WIDTH/2, HEIGHT/2, WIDTH/2,
                            angle_slider.slider,
                            radius_slider.slider,
                            circle_slider.slider);

} // end setup

Draw

p5.draw = function() {
  spiralizer.draw();
  p5.background(255, 5);
}// end draw

Double-Clicked

p5.doubleClicked = function() {
  spiralizer.reset();
  p5.background("white");
} // end doubleClicked

The P5 Instance

To create the animation I'll create a p5 object by passing in the function from the previous section and the div ID to identify where in the page the sketch should go.

new p5(spiral_sketch, SPIRAL_DIV);

References