diff --git a/package-lock.json b/package-lock.json index 83821ba..3bd60f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "pixelmanipulator", - "version": "5.5.1", + "version": "5.5.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1615,6 +1615,12 @@ } } }, + "ava-fast-check": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ava-fast-check/-/ava-fast-check-5.0.0.tgz", + "integrity": "sha512-ZXXVVJ74KntRkG1Iolgiia+UUNU/uN8PZpvae3BiuZVkGW9oyhuy3FUHEEzjYvpkwK7gzNw0rUzsuyLidXPNSQ==", + "dev": true + }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -3335,6 +3341,15 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, + "fast-check": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-2.25.0.tgz", + "integrity": "sha512-wRUT2KD2lAmT75WNIJIHECawoUUMHM0I5jrlLXGtGeqmPL8jl/EldUDjY1VCp6fDY8yflyfUeIOsOBrIbIiArg==", + "dev": true, + "requires": { + "pure-rand": "^5.0.1" + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5306,6 +5321,12 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, + "pure-rand": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.1.tgz", + "integrity": "sha512-ksWccjmXOHU2gJBnH0cK1lSYdvSZ0zLoCMSz/nTGh6hDvCSgcRxDyIcOBD6KNxFz3xhMPm/T267Tbe2JRymKEQ==", + "dev": true + }, "qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", diff --git a/package.json b/package.json index c9313fb..4d03039 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixelmanipulator", - "version": "5.5.1", + "version": "5.5.2", "description": "Run any cellular automata on an html5 canvas.", "main": "dist/main.js", "browser": "dist/browser.js", @@ -68,6 +68,7 @@ "@typescript-eslint/eslint-plugin": "^4.0.1", "@typescript-eslint/parser": "^4.0.0", "ava": "^4.2.0", + "ava-fast-check": "^5.0.0", "bootstrap-dark-5": "^1.1.3", "browserify": "^17.0.0", "c8": "^7.11.2", @@ -78,6 +79,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1 || ^5.0.0", "eslint-plugin-tsdoc": "^0.2.16", + "fast-check": "^2.25.0", "fps-control": "^1.0.0", "gh-pages": "^3.2.3", "parcel": "^2.5.0", diff --git a/src/lib/neighborhoods.ts b/src/lib/neighborhoods.ts index 266a958..4da60c4 100644 --- a/src/lib/neighborhoods.ts +++ b/src/lib/neighborhoods.ts @@ -26,7 +26,7 @@ export type Hitbox=Location[] */ export function rect (topLeft: Location, bottomRight: Location): Location[] { const output: Hitbox = [] - for (let x = topLeft.x; x <= bottomRight.y; x++) { + for (let x = topLeft.x; x <= bottomRight.x; x++) { for (let y = topLeft.y; y <= bottomRight.y; y++) { output.push({ x, y }) } diff --git a/tests/lib/test-neighborhoods.ts b/tests/lib/test-neighborhoods.ts new file mode 100644 index 0000000..dcbf791 --- /dev/null +++ b/tests/lib/test-neighborhoods.ts @@ -0,0 +1,59 @@ +import { testProp, fc } from 'ava-fast-check' +import { neighborhoods } from '../../src/lib/pixelmanipulator' +const { rect } = neighborhoods + +testProp( + 'rect\'s size output is the area', + // Maximums provided to ensure reasonable time and memory is spent + [fc.integer(), fc.integer(), fc.nat({ max: 25 }), fc.nat({ max: 25 })], + (t, x, y, w, h) => { + const topLeft = { + x, + y + } + const bottomRight = { + x: x + w, + y: y + h + } + const expectedArea = (w + 1) * (h + 1) // (0,0) to (1,1) is area of 4 + const list = rect(topLeft, bottomRight) + t.is(list.length, expectedArea) + } +) +testProp( + 'return from rect has no duplicates', + [fc.integer(), fc.integer(), fc.nat({ max: 25 }), fc.nat({ max: 25 })], + (t, x, y, w, h) => { + const topLeft = { + x, + y + } + const bottomRight = { + x: x + w, + y: y + h + } + const list = rect(topLeft, bottomRight).map(pos => JSON.stringify(pos)) + const set = new Set(list) + t.is(list.length, set.size) + } +) +testProp( + 'return from rect keeps all values within bounds', + [fc.integer(), fc.integer(), fc.nat({ max: 25 }), fc.nat({ max: 25 })], + (t, x, y, w, h) => { + const topLeft = { + x, + y + } + const bottomRight = { + x: x + w, + y: y + h + } + const list = rect(topLeft, bottomRight) + t.plan(list.length * 2) + list.forEach(pos => { + t.true(topLeft.x <= pos.x && pos.x <= bottomRight.x) + t.true(topLeft.y <= pos.y && pos.y <= bottomRight.y) + }) + } +)