Addressing some of the technical debt. Not very exciting. So bullet lists.

DID:

  • Set up Jasmine tests in Robotron (8 specs, 0 failures)
  • Set up Jasmine tests in canvas-libs (4 specs, 0 failures)
  • Moved supporting.js stuff to canvas-libs for both Centipede and Robotron
  • Moved the Sound object from sound.js to canvas-libs for both Centipede and Robotron

Commits

Centipede

Robotron

Canvas Libs

Still TODO:

  • Add more tests to Robotron
  • Add more tests to canvas-libs
  • Add tests to Joust
  • Fix broken tests in Centipede. All of the refactoring has resulted in 59 specs, 37 failures :/
  • Research whether it’s possible to run Jasmine tests with a pipeline script in GitLab, so the results can be seen in the repo

The test spec runners

Spec runners are accessible through eash app’s URL, but the latest code is not yet deployed:

Not canvas libs, though! Maybe I’ll add those later.

Local results:

Centipede

Centipede Test Results

Robotron

Robotron Test Results

Notes

I’ll need to come up with a logical naming scheme for overrides of things pulled from canvas-libs. Can I just name it the same thing? Or should I prepend the project name to it, like var robotronControls = controls;?

Init functions

I had this great idea to put all of the initialization scripts inside game.start().

This worked to run the game, but created problems in testing, where any game.start() call would generate a ton of DOM elements, which I’m not really sure I want to be doing during the tests. I could probably block creation of the elements somehow, or find another way to test the start script without actually running it.

The workaround for now is to just move all the init function calls to an init.js script, and load that last, and to call that out explicitly in the index.html so I don’t muck it up later.

1
2
<!-- this needs to be loaded last -->
&lt;script src="app/scripts/init.js"&gt;&lt;/script&gt;

Yawn, where’s the game stuff

Sorry not sorry. This stuff is important. With tests and libraries in place, I can break up tight coupling so the games are easy to understand and easily extensible. This way I’ll know exactly where that thing I just changed will impact the entire game without having to trace logic.

March 16, 2018 Update

centipede-jasmine-update-2018-03-16-300x85.png

Commits

Several more interval-creatures-spec.js tests are passing. The primary issue causing these tests to fail previously was around the restructuring of the manage function. Much of this was offloaded to subfunctions in the refactor, so these new subfunctions had to be spied on.

The refactored manage function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
manage : function() {
  Array.from(Object.keys(this.intervals)).forEach(creature => {
    this.spawnCreatureAtIntervals(creature);
    if (intervalCreatures[creature] == false) {
      // return acts like a continue in a forEach
      return;
    }
    this.clearOutsideCanvas(creature);
    this.update(creature);
    this.dropMushrooms(creature);
  });
},

The setup mock function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function mockTestObj() {
  testObj.init();
  spyOn(testObj, 'spawnCreatureAtIntervals');
  spyOn(testObj, 'clearOutsideCanvas');
  spyOn(testObj, 'update');
  spyOn(testObj, 'dropMushrooms');
  testObj.worms = [];
  testObj.flies = [];
  testObj.intervals['worms'] = 10;
};

The test, before:

1
2
3
4
5
it('manage calls intervalCreatures.clearOutsideCanvas', () => {
  spyOn(testObj, 'clearOutsideCanvas');
  testObj.manage();
  expect(testObj.clearOutsideCanvas).toHaveBeenCalled();
});

The error (it’s complaining about intervalCreatures.intervals being undefined, because that gets set in intervalCreatures.init(), which never gets called. First, I executed init(), but eventually opted just to set dummy data in the interval property):

1
2
Testing intervalCreatures functions manage calls intervalCreatures.clearOutsideCanvas
TypeError: Cannot convert undefined or null to object

The test, after:

1
2
3
4
5
6
it('manage calls intervalCreatures.clearOutsideCanvas', () => {
  mockTestObj();
  testObj.worms.push('a worm');
  testObj.manage();
  expect(testObj.clearOutsideCanvas).toHaveBeenCalled();
});

The default objects had to be set in the beforeEach stage to avoid undefined reference errors.

Object initialization (using Object.assign) was also moved into beforeEach, to ensure a fresh copy of the object is being used for each test.

1
2
3
4
5
6
7
beforeEach(function () {
  testObj = Object.assign({}, intervalCreatures);
  supporting = Object.assign({}, supporting);
  knobsAndLevers = Object.assign({}, knobsAndLevers);
  game = {'gameArea' : {'frameNo' : 10}};
  game.gameArea.canvas = {height: 800, width: 800};
});

This highlighted a problem in interval-creatures.js, where a static reference was being used instead of an instance reference, causing the creature arrays (flies, worms) to be empty, even after adding a value to the array during the test setup, with testObj.intervals['worms'] = 10;. And the empty creature arrays passed the intervalCreatures[creature] == false test, so manage never got to clearOutsideCanvas.

Before:

1
2
3
4
5
6
...
if (this[creature] == false) {
  // return acts like a continue in a forEach
  return;
}
...

After:

1
2
3
4
5
6
7
...
if (intervalCreatures[creature] == false) {
  // return acts like a continue in a forEach
  return;
}
...