From 0dd072b9ac070b8606a9f7146e3a0fb031e34475 Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Wed, 23 Jul 2025 14:49:49 +0200 Subject: [PATCH 01/18] fix failing unit test fix the failing unit test for gilded-rose.spec.ts --- TypeScript/test/jest/gilded-rose.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TypeScript/test/jest/gilded-rose.spec.ts b/TypeScript/test/jest/gilded-rose.spec.ts index 65330750..0570792a 100644 --- a/TypeScript/test/jest/gilded-rose.spec.ts +++ b/TypeScript/test/jest/gilded-rose.spec.ts @@ -1,9 +1,9 @@ -import { Item, GildedRose } from '@/gilded-rose'; +import { Item, GildedRose } from "@/gilded-rose"; -describe('Gilded Rose', () => { - it('should foo', () => { - const gildedRose = new GildedRose([new Item('foo', 0, 0)]); +describe("Gilded Rose", () => { + it("should foo", () => { + const gildedRose = new GildedRose([new Item("foo", 0, 0)]); const items = gildedRose.updateQuality(); - expect(items[0].name).toBe('fixme'); + expect(items[0].name).toBe("foo"); }); }); From 843a2c639b914935ecce23fea2d3b9ce2eec28e7 Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Wed, 23 Jul 2025 14:51:12 +0200 Subject: [PATCH 02/18] cleanup unused test frameworks --- TypeScript/.mocharc.js | 11 -------- TypeScript/.nycrc.js | 17 ------------ TypeScript/package.json | 16 +++--------- TypeScript/test/mocha/gilded-rose.spec.ts | 10 -------- TypeScript/test/vitest/approvals.spec.ts | 30 ---------------------- TypeScript/test/vitest/gilded-rose.spec.ts | 9 ------- TypeScript/vitest.config.ts | 14 ---------- 7 files changed, 3 insertions(+), 104 deletions(-) delete mode 100644 TypeScript/.mocharc.js delete mode 100644 TypeScript/.nycrc.js delete mode 100644 TypeScript/test/mocha/gilded-rose.spec.ts delete mode 100644 TypeScript/test/vitest/approvals.spec.ts delete mode 100644 TypeScript/test/vitest/gilded-rose.spec.ts delete mode 100644 TypeScript/vitest.config.ts diff --git a/TypeScript/.mocharc.js b/TypeScript/.mocharc.js deleted file mode 100644 index 9ae39f77..00000000 --- a/TypeScript/.mocharc.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -module.exports = { - require: [ - "ts-node/register", - "tsconfig-paths/register", - "source-map-support/register" - ], - recursive: true, - spec: "test/mocha/*.spec.ts" -} diff --git a/TypeScript/.nycrc.js b/TypeScript/.nycrc.js deleted file mode 100644 index 3b19d56e..00000000 --- a/TypeScript/.nycrc.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - extension: [ - ".ts" - ], - exclude: [ - "**/*.d.ts", - "test/**", - "/*.js" - ], - require: [ - "ts-node/register" - ], - reporter: [ - "html", - "text" - ] -} diff --git a/TypeScript/package.json b/TypeScript/package.json index c7c597e3..b5e53907 100644 --- a/TypeScript/package.json +++ b/TypeScript/package.json @@ -6,30 +6,20 @@ "precompile": "rimraf app/**/*.js test/**/*.js", "compile": "tsc", "pretest": "rimraf app/**/*.js test/**/*.js", - "test:jest": "jest", - "test:jest:watch": "jest --watchAll", - "test:mocha": "nyc mocha", - "test:vitest": "vitest --coverage" + "test": "jest", + "test:watch": "jest --watchAll" }, "license": "MIT", "private": true, "devDependencies": { - "@types/chai": "^4.2.22", "@types/jest": "^29.4.0", - "@types/mocha": "^10.0.1", "@types/node": "^18.14.0", - "@vitest/coverage-istanbul": "^0.28.5", - "chai": "^4.3.4", "jest": "^29.4.3", - "mocha": "^10.2.0", - "nyc": "~15.1.0", "rimraf": "^4.1.2", "source-map-support": "^0.5.20", "ts-jest": "^29.0.5", "ts-node": "^10.9.1", "tsconfig-paths": "^4.1.2", - "typescript": "^4.4.4", - "vite-tsconfig-paths": "^4.0.5", - "vitest": "^0.28.5" + "typescript": "^4.4.4" } } diff --git a/TypeScript/test/mocha/gilded-rose.spec.ts b/TypeScript/test/mocha/gilded-rose.spec.ts deleted file mode 100644 index 02cd24c5..00000000 --- a/TypeScript/test/mocha/gilded-rose.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { expect } from 'chai'; -import { Item, GildedRose } from '@/gilded-rose'; - -describe('Gilded Rose', () => { - it('should foo', () => { - const gildedRose = new GildedRose([new Item('foo', 0, 0)]); - const items = gildedRose.updateQuality(); - expect(items[0].name).to.equal('fixme'); - }); -}); diff --git a/TypeScript/test/vitest/approvals.spec.ts b/TypeScript/test/vitest/approvals.spec.ts deleted file mode 100644 index 858f4a22..00000000 --- a/TypeScript/test/vitest/approvals.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { execSync } from 'node:child_process'; -import { Item, GildedRose } from '@/gilded-rose'; - -/** - * This test uses Vitest Snapshot, similar to [Jest Snapshot](https://goo.gl/fbAQLP). - * - * There are two test cases here with different styles: - *
  • "foo" is more similar to the unit test from the 'Java' version - *
  • "thirtyDays" is more similar to the TextTest from the 'Java' version - * - * I suggest choosing one style to develop and deleting the other. - */ - -describe('Gilded Rose Approval', () => { - it('should foo', () => { - const gildedRose = new GildedRose([new Item('foo', 0, 0)]); - const items = gildedRose.updateQuality(); - - expect(items).toMatchSnapshot(); - }); - - it('should thirtyDays', () => { - const consoleOutput = execSync( - 'ts-node test/golden-master-text-test.ts 30', - { encoding: 'utf-8' } - ); - - expect(consoleOutput).toMatchSnapshot(); - }); -}); diff --git a/TypeScript/test/vitest/gilded-rose.spec.ts b/TypeScript/test/vitest/gilded-rose.spec.ts deleted file mode 100644 index 65330750..00000000 --- a/TypeScript/test/vitest/gilded-rose.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Item, GildedRose } from '@/gilded-rose'; - -describe('Gilded Rose', () => { - it('should foo', () => { - const gildedRose = new GildedRose([new Item('foo', 0, 0)]); - const items = gildedRose.updateQuality(); - expect(items[0].name).toBe('fixme'); - }); -}); diff --git a/TypeScript/vitest.config.ts b/TypeScript/vitest.config.ts deleted file mode 100644 index 73488c68..00000000 --- a/TypeScript/vitest.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {defineConfig} from 'vitest/config'; -import tsconfigPaths from 'vite-tsconfig-paths'; - -export default defineConfig({ - plugins: [tsconfigPaths()], - test: { - globals: true, - include: ['test/vitest/**/*.{spec,test}.{js,ts}'], - coverage: { - provider: 'istanbul', - reporter: ['text', 'html'] - } - }, -}); From 40c997220d26325b3038d02cdf96a9430c95ced6 Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Wed, 23 Jul 2025 15:15:32 +0200 Subject: [PATCH 03/18] refactor snapshot tests - refactor snapshot tests to not use the console output and text test. - Remove texttest rig python script - generate snapshot file --- TypeScript/test/golden-master-text-test.ts | 34 - .../jest/__snapshots__/approvals.spec.ts.snap | 1501 +++++++++++++++++ TypeScript/test/jest/approvals.spec.ts | 67 +- TypeScript/texttest_rig.py | 14 - 4 files changed, 1521 insertions(+), 95 deletions(-) delete mode 100644 TypeScript/test/golden-master-text-test.ts create mode 100644 TypeScript/test/jest/__snapshots__/approvals.spec.ts.snap delete mode 100644 TypeScript/texttest_rig.py diff --git a/TypeScript/test/golden-master-text-test.ts b/TypeScript/test/golden-master-text-test.ts deleted file mode 100644 index 378f78d5..00000000 --- a/TypeScript/test/golden-master-text-test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Item, GildedRose } from '../app/gilded-rose'; - -console.log("OMGHAI!") - -const items = [ - new Item("+5 Dexterity Vest", 10, 20), // - new Item("Aged Brie", 2, 0), // - new Item("Elixir of the Mongoose", 5, 7), // - new Item("Sulfuras, Hand of Ragnaros", 0, 80), // - new Item("Sulfuras, Hand of Ragnaros", -1, 80), - new Item("Backstage passes to a TAFKAL80ETC concert", 15, 20), - new Item("Backstage passes to a TAFKAL80ETC concert", 10, 49), - new Item("Backstage passes to a TAFKAL80ETC concert", 5, 49), - // this conjured item does not work properly yet - new Item("Conjured Mana Cake", 3, 6)]; - - -const gildedRose = new GildedRose(items); - -let days: number = 2; -if (process.argv.length > 2) { - days = +process.argv[2]; - } - -for (let i = 0; i < days + 1; i++) { - console.log("-------- day " + i + " --------"); - console.log("name, sellIn, quality"); - items.forEach(element => { - console.log(element.name + ', ' + element.sellIn + ', ' + element.quality); - - }); - console.log(); - gildedRose.updateQuality(); -} diff --git a/TypeScript/test/jest/__snapshots__/approvals.spec.ts.snap b/TypeScript/test/jest/__snapshots__/approvals.spec.ts.snap new file mode 100644 index 00000000..6b0fc024 --- /dev/null +++ b/TypeScript/test/jest/__snapshots__/approvals.spec.ts.snap @@ -0,0 +1,1501 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 1`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 19, + "sellIn": 9, + }, + Item { + "name": "Aged Brie", + "quality": 1, + "sellIn": 1, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 6, + "sellIn": 4, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 21, + "sellIn": 14, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 9, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 4, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 5, + "sellIn": 2, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 2`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 18, + "sellIn": 8, + }, + Item { + "name": "Aged Brie", + "quality": 2, + "sellIn": 0, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 5, + "sellIn": 3, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 22, + "sellIn": 13, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 8, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 3, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 4, + "sellIn": 1, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 3`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 17, + "sellIn": 7, + }, + Item { + "name": "Aged Brie", + "quality": 4, + "sellIn": -1, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 4, + "sellIn": 2, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 23, + "sellIn": 12, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 7, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 2, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 3, + "sellIn": 0, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 4`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 16, + "sellIn": 6, + }, + Item { + "name": "Aged Brie", + "quality": 6, + "sellIn": -2, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 3, + "sellIn": 1, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 24, + "sellIn": 11, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 6, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 1, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 1, + "sellIn": -1, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 5`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 15, + "sellIn": 5, + }, + Item { + "name": "Aged Brie", + "quality": 8, + "sellIn": -3, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 2, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 25, + "sellIn": 10, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 5, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 0, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -2, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 6`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 14, + "sellIn": 4, + }, + Item { + "name": "Aged Brie", + "quality": 10, + "sellIn": -4, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -1, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 27, + "sellIn": 9, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 4, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -1, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -3, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 7`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 13, + "sellIn": 3, + }, + Item { + "name": "Aged Brie", + "quality": 12, + "sellIn": -5, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -2, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 29, + "sellIn": 8, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 3, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -2, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -4, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 8`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 12, + "sellIn": 2, + }, + Item { + "name": "Aged Brie", + "quality": 14, + "sellIn": -6, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -3, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 31, + "sellIn": 7, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 2, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -3, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -5, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 9`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 11, + "sellIn": 1, + }, + Item { + "name": "Aged Brie", + "quality": 16, + "sellIn": -7, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -4, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 33, + "sellIn": 6, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -4, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -6, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 10`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 10, + "sellIn": 0, + }, + Item { + "name": "Aged Brie", + "quality": 18, + "sellIn": -8, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -5, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 35, + "sellIn": 5, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 0, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -5, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -7, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 11`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 8, + "sellIn": -1, + }, + Item { + "name": "Aged Brie", + "quality": 20, + "sellIn": -9, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -6, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 38, + "sellIn": 4, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -6, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -8, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 12`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 6, + "sellIn": -2, + }, + Item { + "name": "Aged Brie", + "quality": 22, + "sellIn": -10, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -7, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 41, + "sellIn": 3, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -2, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -7, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -9, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 13`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 4, + "sellIn": -3, + }, + Item { + "name": "Aged Brie", + "quality": 24, + "sellIn": -11, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -8, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 44, + "sellIn": 2, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -3, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -8, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -10, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 14`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 2, + "sellIn": -4, + }, + Item { + "name": "Aged Brie", + "quality": 26, + "sellIn": -12, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -9, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 47, + "sellIn": 1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -4, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -9, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -11, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 15`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -5, + }, + Item { + "name": "Aged Brie", + "quality": 28, + "sellIn": -13, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -10, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 50, + "sellIn": 0, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -5, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -10, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -12, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 16`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -6, + }, + Item { + "name": "Aged Brie", + "quality": 30, + "sellIn": -14, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -11, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -6, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -11, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -13, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 17`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -7, + }, + Item { + "name": "Aged Brie", + "quality": 32, + "sellIn": -15, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -12, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -2, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -7, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -12, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -14, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 18`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -8, + }, + Item { + "name": "Aged Brie", + "quality": 34, + "sellIn": -16, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -13, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -3, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -8, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -13, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -15, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 19`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -9, + }, + Item { + "name": "Aged Brie", + "quality": 36, + "sellIn": -17, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -14, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -4, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -9, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -14, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -16, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 20`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -10, + }, + Item { + "name": "Aged Brie", + "quality": 38, + "sellIn": -18, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -15, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -5, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -10, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -15, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -17, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 21`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -11, + }, + Item { + "name": "Aged Brie", + "quality": 40, + "sellIn": -19, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -16, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -6, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -11, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -16, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -18, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 22`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -12, + }, + Item { + "name": "Aged Brie", + "quality": 42, + "sellIn": -20, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -17, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -7, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -12, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -17, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -19, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 23`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -13, + }, + Item { + "name": "Aged Brie", + "quality": 44, + "sellIn": -21, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -18, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -8, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -13, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -18, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -20, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 24`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -14, + }, + Item { + "name": "Aged Brie", + "quality": 46, + "sellIn": -22, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -19, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -9, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -14, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -19, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -21, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 25`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -15, + }, + Item { + "name": "Aged Brie", + "quality": 48, + "sellIn": -23, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -20, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -10, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -15, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -20, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -22, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 26`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -16, + }, + Item { + "name": "Aged Brie", + "quality": 50, + "sellIn": -24, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -21, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -11, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -16, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -21, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -23, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 27`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -17, + }, + Item { + "name": "Aged Brie", + "quality": 50, + "sellIn": -25, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -22, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -12, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -17, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -22, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -24, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 28`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -18, + }, + Item { + "name": "Aged Brie", + "quality": 50, + "sellIn": -26, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -23, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -13, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -18, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -23, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -25, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 29`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -19, + }, + Item { + "name": "Aged Brie", + "quality": 50, + "sellIn": -27, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -24, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -14, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -19, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -24, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -26, + }, +] +`; + +exports[`Gilded Rose Approval should match the snapshot for thirty Days 30`] = ` +[ + Item { + "name": "+5 Dexterity Vest", + "quality": 0, + "sellIn": -20, + }, + Item { + "name": "Aged Brie", + "quality": 50, + "sellIn": -28, + }, + Item { + "name": "Elixir of the Mongoose", + "quality": 0, + "sellIn": -25, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": 0, + }, + Item { + "name": "Sulfuras, Hand of Ragnaros", + "quality": 80, + "sellIn": -1, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -15, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -20, + }, + Item { + "name": "Backstage passes to a TAFKAL80ETC concert", + "quality": 0, + "sellIn": -25, + }, + Item { + "name": "Conjured Mana Cake", + "quality": 0, + "sellIn": -27, + }, +] +`; diff --git a/TypeScript/test/jest/approvals.spec.ts b/TypeScript/test/jest/approvals.spec.ts index bb7f45f9..ca6ba5d1 100644 --- a/TypeScript/test/jest/approvals.spec.ts +++ b/TypeScript/test/jest/approvals.spec.ts @@ -1,54 +1,27 @@ -import { Item, GildedRose } from '@/gilded-rose'; +import { Item, GildedRose } from "@/gilded-rose"; -/** - * This unit test uses [Jest Snapshot](https://goo.gl/fbAQLP). - * - * There are two test cases here with different styles: - *
  • "foo" is more similar to the unit test from the 'Java' version - *
  • "thirtyDays" is more similar to the TextTest from the 'Java' version - * - * I suggest choosing one style to develop and deleting the other. - */ +describe("Gilded Rose Approval", () => { + it("should match the snapshot for thirty Days", () => { + const items = [ + new Item("+5 Dexterity Vest", 10, 20), // + new Item("Aged Brie", 2, 0), // + new Item("Elixir of the Mongoose", 5, 7), // + new Item("Sulfuras, Hand of Ragnaros", 0, 80), // + new Item("Sulfuras, Hand of Ragnaros", -1, 80), + new Item("Backstage passes to a TAFKAL80ETC concert", 15, 20), + new Item("Backstage passes to a TAFKAL80ETC concert", 10, 49), + new Item("Backstage passes to a TAFKAL80ETC concert", 5, 49), + // this conjured item does not work properly yet + new Item("Conjured Mana Cake", 3, 6), + ]; -describe('Gilded Rose Approval', () => { + const gildedRose = new GildedRose(items); - let gameConsoleOutput: string; - let originalConsoleLog: (message: any) => void; - let originalProcessArgv: string[] + const days = 30; - function gameConsoleLog(msg: string) { - if (msg) { - gameConsoleOutput += msg; + for (let i = 0; i < days; i++) { + const updatedItems = gildedRose.updateQuality(); + expect(updatedItems).toMatchSnapshot(); } - gameConsoleOutput += "\n"; - } - - beforeEach(() => { - // prepare capturing console.log to our own gameConsoleLog. - gameConsoleOutput = ""; - originalConsoleLog = console.log; - console.log = gameConsoleLog; - originalProcessArgv = process.argv; }); - - afterEach(() => { - // reset original console.log - console.log = originalConsoleLog; - process.argv = originalProcessArgv; - }); - - it('should foo', () => { - const gildedRose = new GildedRose([new Item('foo', 0, 0)]); - const items = gildedRose.updateQuality(); - - expect(items).toMatchSnapshot(); - }); - - it('should thirtyDays', () => { - process.argv = ["", " Date: Thu, 24 Jul 2025 09:49:27 +0200 Subject: [PATCH 04/18] write unit tests for currently implemented code --- TypeScript/test/jest/gilded-rose.spec.ts | 147 ++++++++++++++++++++++- 1 file changed, 144 insertions(+), 3 deletions(-) diff --git a/TypeScript/test/jest/gilded-rose.spec.ts b/TypeScript/test/jest/gilded-rose.spec.ts index 0570792a..92c3156f 100644 --- a/TypeScript/test/jest/gilded-rose.spec.ts +++ b/TypeScript/test/jest/gilded-rose.spec.ts @@ -1,9 +1,150 @@ import { Item, GildedRose } from "@/gilded-rose"; describe("Gilded Rose", () => { - it("should foo", () => { - const gildedRose = new GildedRose([new Item("foo", 0, 0)]); + it("should have an empty array as items when no constructor parameter is provided", () => { + const gildedRose = new GildedRose(); + expect(gildedRose.items).toEqual([]); + }); + + it("should degrade sell inn and quality each day", () => { + const gildedRose = new GildedRose([new Item("standard item", 1, 1)]); + const items = gildedRose.updateQuality(); - expect(items[0].name).toBe("foo"); + + expect(items[0]).toMatchObject({ + name: "standard item", + sellIn: 0, + quality: 0, + }); + }); + + it("should degrade quality twice as fast after sell in date", () => { + const gildedRose = new GildedRose([new Item("standard item", 0, 2)]); + + const items = gildedRose.updateQuality(); + + expect(items[0]).toMatchObject({ + name: "standard item", + sellIn: -1, + quality: 0, + }); + }); + + it("should not degrade quality below 0", () => { + const gildedRose = new GildedRose([new Item("standard item", 1, 0)]); + + const items = gildedRose.updateQuality(); + + expect(items[0]).toMatchObject({ + name: "standard item", + sellIn: 0, + quality: 0, + }); + }); + + it("should increase quality of Aged Brie as it gets older", () => { + const gildedRose = new GildedRose([new Item("Aged Brie", 1, 1)]); + + const items = gildedRose.updateQuality(); + + expect(items[0]).toMatchObject({ + name: "Aged Brie", + sellIn: 0, + quality: 2, + }); + }); + + it("should increase quality of Aged Brie twice as fast after sell in date", () => { + const gildedRose = new GildedRose([new Item("Aged Brie", 0, 1)]); + + const items = gildedRose.updateQuality(); + + expect(items[0]).toMatchObject({ + name: "Aged Brie", + sellIn: -1, + quality: 3, + }); + }); + + it("should never increase quality of Aged Brie over 50", () => { + const gildedRose = new GildedRose([new Item("Aged Brie", 0, 50)]); + + const items = gildedRose.updateQuality(); + + expect(items[0]).toMatchObject({ + name: "Aged Brie", + sellIn: -1, + quality: 50, + }); + }); + + it("should not change quality of Sulfuras", () => { + const gildedRose = new GildedRose([ + new Item("Sulfuras, Hand of Ragnaros", 0, 80), + ]); + + const items = gildedRose.updateQuality(); + + expect(items[0]).toMatchObject({ + name: "Sulfuras, Hand of Ragnaros", + sellIn: 0, + quality: 80, + }); + }); + + it("should increase quality of Backstage passes by 1 if sell in date is more than 10 days away", () => { + const gildedRose = new GildedRose([ + new Item("Backstage passes to a TAFKAL80ETC concert", 11, 20), + ]); + + const items = gildedRose.updateQuality(); + + expect(items[0]).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: 10, + quality: 21, + }); + }); + + it("should increase quality of Backstage passes by 2 if sell in date is less than 10 days away but more than 5", () => { + const gildedRose = new GildedRose([ + new Item("Backstage passes to a TAFKAL80ETC concert", 9, 20), + ]); + + const items = gildedRose.updateQuality(); + + expect(items[0]).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: 8, + quality: 22, + }); + }); + + it("should increase quality of Backstage passes by 3 if sell in date is less than 5 days away", () => { + const gildedRose = new GildedRose([ + new Item("Backstage passes to a TAFKAL80ETC concert", 4, 20), + ]); + + const items = gildedRose.updateQuality(); + + expect(items[0]).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: 3, + quality: 23, + }); + }); + + it("should drop quality of Backstage passes to 0 after sell in date", () => { + const gildedRose = new GildedRose([ + new Item("Backstage passes to a TAFKAL80ETC concert", 0, 20), + ]); + + const items = gildedRose.updateQuality(); + + expect(items[0]).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: -1, + quality: 0, + }); }); }); From dcbf92c3aadf803310e0ce6d2ecdfacfac788c71 Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 10:17:21 +0200 Subject: [PATCH 05/18] Use a modern EcmaScript version --- TypeScript/tsconfig.json | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/TypeScript/tsconfig.json b/TypeScript/tsconfig.json index d2922795..1623eedf 100644 --- a/TypeScript/tsconfig.json +++ b/TypeScript/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es5", + "target": "es2022", "strict": true, "noImplicitAny": false, "sourceMap": true, @@ -9,12 +9,8 @@ "resolveJsonModule": true, "esModuleInterop": true, "paths": { - "@/*": [ - "app/*" - ] + "@/*": ["app/*"] } }, - "exclude": [ - "node_modules" - ] + "exclude": ["node_modules"] } From 3872d781060d04f369999872f5bf2167499a990e Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 09:12:55 +0200 Subject: [PATCH 06/18] Add Todo for new functionality --- TypeScript/test/jest/gilded-rose.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TypeScript/test/jest/gilded-rose.spec.ts b/TypeScript/test/jest/gilded-rose.spec.ts index 92c3156f..391ee0c0 100644 --- a/TypeScript/test/jest/gilded-rose.spec.ts +++ b/TypeScript/test/jest/gilded-rose.spec.ts @@ -147,4 +147,6 @@ describe("Gilded Rose", () => { quality: 0, }); }); + + // to implement: "Conjured" items degrade in Quality twice as fast as normal items }); From ba40febb071f86879433206ece662127f2e4a6fb Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 09:53:13 +0200 Subject: [PATCH 07/18] Add some comments to code that can't be changed because of requirements --- TypeScript/app/gilded-rose.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/TypeScript/app/gilded-rose.ts b/TypeScript/app/gilded-rose.ts index db58d678..b1725f20 100644 --- a/TypeScript/app/gilded-rose.ts +++ b/TypeScript/app/gilded-rose.ts @@ -3,6 +3,13 @@ export class Item { sellIn: number; quality: number; + // can't edit this constructor because of the kata rules. + // but I would change the constructor to take a javaScript object. + // It makes the consumer code more readable. + // e.g. new Item({ name: "standard item", sellIn: 0, quality: 2 }) + // instead of new Item("standard item", 0, 2) + + // I would also type the parameters, because now they are implicitly any. constructor(name, sellIn, quality) { this.name = name; this.sellIn = sellIn; @@ -11,6 +18,8 @@ export class Item { } export class GildedRose { + // also can't edit this because of the kata rules. + // But I prefer typing this as : Item[] items: Array; constructor(items = [] as Array) { From 6445f7f797e299be7cc1a5c64597c6af29a9ce4f Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 09:16:30 +0200 Subject: [PATCH 08/18] Use strict equality operator over loose equality operator Use strict equality operator over loose equality operator because it is seen as a best practice in the js/ts community. --- TypeScript/app/gilded-rose.ts | 36 +++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/TypeScript/app/gilded-rose.ts b/TypeScript/app/gilded-rose.ts index b1725f20..ec735003 100644 --- a/TypeScript/app/gilded-rose.ts +++ b/TypeScript/app/gilded-rose.ts @@ -28,46 +28,54 @@ export class GildedRose { updateQuality() { for (let i = 0; i < this.items.length; i++) { - if (this.items[i].name != 'Aged Brie' && this.items[i].name != 'Backstage passes to a TAFKAL80ETC concert') { + if ( + this.items[i].name !== "Aged Brie" && + this.items[i].name !== "Backstage passes to a TAFKAL80ETC concert" + ) { if (this.items[i].quality > 0) { - if (this.items[i].name != 'Sulfuras, Hand of Ragnaros') { - this.items[i].quality = this.items[i].quality - 1 + if (this.items[i].name !== "Sulfuras, Hand of Ragnaros") { + this.items[i].quality = this.items[i].quality - 1; } } } else { if (this.items[i].quality < 50) { - this.items[i].quality = this.items[i].quality + 1 - if (this.items[i].name == 'Backstage passes to a TAFKAL80ETC concert') { + this.items[i].quality = this.items[i].quality + 1; + if ( + this.items[i].name === "Backstage passes to a TAFKAL80ETC concert" + ) { if (this.items[i].sellIn < 11) { if (this.items[i].quality < 50) { - this.items[i].quality = this.items[i].quality + 1 + this.items[i].quality = this.items[i].quality + 1; } } if (this.items[i].sellIn < 6) { if (this.items[i].quality < 50) { - this.items[i].quality = this.items[i].quality + 1 + this.items[i].quality = this.items[i].quality + 1; } } } } } - if (this.items[i].name != 'Sulfuras, Hand of Ragnaros') { + if (this.items[i].name !== "Sulfuras, Hand of Ragnaros") { this.items[i].sellIn = this.items[i].sellIn - 1; } if (this.items[i].sellIn < 0) { - if (this.items[i].name != 'Aged Brie') { - if (this.items[i].name != 'Backstage passes to a TAFKAL80ETC concert') { + if (this.items[i].name !== "Aged Brie") { + if ( + this.items[i].name !== "Backstage passes to a TAFKAL80ETC concert" + ) { if (this.items[i].quality > 0) { - if (this.items[i].name != 'Sulfuras, Hand of Ragnaros') { - this.items[i].quality = this.items[i].quality - 1 + if (this.items[i].name !== "Sulfuras, Hand of Ragnaros") { + this.items[i].quality = this.items[i].quality - 1; } } } else { - this.items[i].quality = this.items[i].quality - this.items[i].quality + this.items[i].quality = + this.items[i].quality - this.items[i].quality; } } else { if (this.items[i].quality < 50) { - this.items[i].quality = this.items[i].quality + 1 + this.items[i].quality = this.items[i].quality + 1; } } } From 737704e19eed528c3b4725bd3754dcad91fa3e1c Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 09:29:15 +0200 Subject: [PATCH 09/18] Use Array.prototype.map over a for loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use Array.prototype.map over a for loop so that we don’t have to use array indexing all the time. --- TypeScript/app/gilded-rose.ts | 62 ++++++++++++++++------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/TypeScript/app/gilded-rose.ts b/TypeScript/app/gilded-rose.ts index ec735003..fe63cc89 100644 --- a/TypeScript/app/gilded-rose.ts +++ b/TypeScript/app/gilded-rose.ts @@ -27,60 +27,54 @@ export class GildedRose { } updateQuality() { - for (let i = 0; i < this.items.length; i++) { + return this.items.map((item) => { if ( - this.items[i].name !== "Aged Brie" && - this.items[i].name !== "Backstage passes to a TAFKAL80ETC concert" + item.name !== "Aged Brie" && + item.name !== "Backstage passes to a TAFKAL80ETC concert" ) { - if (this.items[i].quality > 0) { - if (this.items[i].name !== "Sulfuras, Hand of Ragnaros") { - this.items[i].quality = this.items[i].quality - 1; + if (item.quality > 0) { + if (item.name !== "Sulfuras, Hand of Ragnaros") { + item.quality = item.quality - 1; } } } else { - if (this.items[i].quality < 50) { - this.items[i].quality = this.items[i].quality + 1; - if ( - this.items[i].name === "Backstage passes to a TAFKAL80ETC concert" - ) { - if (this.items[i].sellIn < 11) { - if (this.items[i].quality < 50) { - this.items[i].quality = this.items[i].quality + 1; + if (item.quality < 50) { + item.quality = item.quality + 1; + if (item.name === "Backstage passes to a TAFKAL80ETC concert") { + if (item.sellIn < 11) { + if (item.quality < 50) { + item.quality = item.quality + 1; } } - if (this.items[i].sellIn < 6) { - if (this.items[i].quality < 50) { - this.items[i].quality = this.items[i].quality + 1; + if (item.sellIn < 6) { + if (item.quality < 50) { + item.quality = item.quality + 1; } } } } } - if (this.items[i].name !== "Sulfuras, Hand of Ragnaros") { - this.items[i].sellIn = this.items[i].sellIn - 1; + if (item.name !== "Sulfuras, Hand of Ragnaros") { + item.sellIn = item.sellIn - 1; } - if (this.items[i].sellIn < 0) { - if (this.items[i].name !== "Aged Brie") { - if ( - this.items[i].name !== "Backstage passes to a TAFKAL80ETC concert" - ) { - if (this.items[i].quality > 0) { - if (this.items[i].name !== "Sulfuras, Hand of Ragnaros") { - this.items[i].quality = this.items[i].quality - 1; + if (item.sellIn < 0) { + if (item.name !== "Aged Brie") { + if (item.name !== "Backstage passes to a TAFKAL80ETC concert") { + if (item.quality > 0) { + if (item.name !== "Sulfuras, Hand of Ragnaros") { + item.quality = item.quality - 1; } } } else { - this.items[i].quality = - this.items[i].quality - this.items[i].quality; + item.quality = item.quality - item.quality; } } else { - if (this.items[i].quality < 50) { - this.items[i].quality = this.items[i].quality + 1; + if (item.quality < 50) { + item.quality = item.quality + 1; } } } - } - - return this.items; + return item; + }); } } From f35d2e9c848df9650376979e3495fbc3976962fc Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 11:49:26 +0200 Subject: [PATCH 10/18] Introduce improved file structure, move update logic to legacyBehavior class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - move item to it’s own file. - Change tsconfig to use ‘@app’ prefix for imports - move the logic to the legacy-behavior file, allowing to refactor out item type by item type --- TypeScript/app/gilded-rose.ts | 75 +++---------------- TypeScript/app/item.ts | 18 +++++ TypeScript/app/update-behaviors/index.ts | 2 + .../app/update-behaviors/legacy-behavior.ts | 56 ++++++++++++++ .../update-behavior.interface.ts | 5 ++ TypeScript/test/jest/approvals.spec.ts | 3 +- TypeScript/test/jest/gilded-rose.spec.ts | 3 +- TypeScript/tsconfig.json | 2 +- 8 files changed, 96 insertions(+), 68 deletions(-) create mode 100644 TypeScript/app/item.ts create mode 100644 TypeScript/app/update-behaviors/index.ts create mode 100644 TypeScript/app/update-behaviors/legacy-behavior.ts create mode 100644 TypeScript/app/update-behaviors/update-behavior.interface.ts diff --git a/TypeScript/app/gilded-rose.ts b/TypeScript/app/gilded-rose.ts index fe63cc89..2d894720 100644 --- a/TypeScript/app/gilded-rose.ts +++ b/TypeScript/app/gilded-rose.ts @@ -1,21 +1,5 @@ -export class Item { - name: string; - sellIn: number; - quality: number; - - // can't edit this constructor because of the kata rules. - // but I would change the constructor to take a javaScript object. - // It makes the consumer code more readable. - // e.g. new Item({ name: "standard item", sellIn: 0, quality: 2 }) - // instead of new Item("standard item", 0, 2) - - // I would also type the parameters, because now they are implicitly any. - constructor(name, sellIn, quality) { - this.name = name; - this.sellIn = sellIn; - this.quality = quality; - } -} +import { Item } from "@app/item"; +import { IUpdateBehavior, LegacyBehavior } from "@app/update-behaviors"; export class GildedRose { // also can't edit this because of the kata rules. @@ -28,53 +12,14 @@ export class GildedRose { updateQuality() { return this.items.map((item) => { - if ( - item.name !== "Aged Brie" && - item.name !== "Backstage passes to a TAFKAL80ETC concert" - ) { - if (item.quality > 0) { - if (item.name !== "Sulfuras, Hand of Ragnaros") { - item.quality = item.quality - 1; - } - } - } else { - if (item.quality < 50) { - item.quality = item.quality + 1; - if (item.name === "Backstage passes to a TAFKAL80ETC concert") { - if (item.sellIn < 11) { - if (item.quality < 50) { - item.quality = item.quality + 1; - } - } - if (item.sellIn < 6) { - if (item.quality < 50) { - item.quality = item.quality + 1; - } - } - } - } - } - if (item.name !== "Sulfuras, Hand of Ragnaros") { - item.sellIn = item.sellIn - 1; - } - if (item.sellIn < 0) { - if (item.name !== "Aged Brie") { - if (item.name !== "Backstage passes to a TAFKAL80ETC concert") { - if (item.quality > 0) { - if (item.name !== "Sulfuras, Hand of Ragnaros") { - item.quality = item.quality - 1; - } - } - } else { - item.quality = item.quality - item.quality; - } - } else { - if (item.quality < 50) { - item.quality = item.quality + 1; - } - } - } - return item; + return this.#getUpdateBehaviorFor(item).update(); }); } + + #getUpdateBehaviorFor(item: Item): IUpdateBehavior { + switch (item.name) { + default: + return new LegacyBehavior(item); + } + } } diff --git a/TypeScript/app/item.ts b/TypeScript/app/item.ts new file mode 100644 index 00000000..5bad4d89 --- /dev/null +++ b/TypeScript/app/item.ts @@ -0,0 +1,18 @@ +export class Item { + name: string; + sellIn: number; + quality: number; + + // can't edit this constructor because of the kata rules. + // but I would change the constructor to take a javaScript object. + // It makes the consumer code more readable. + // e.g. new Item({ name: "standard item", sellIn: 0, quality: 2 }) + // instead of new Item("standard item", 0, 2) + + // I would also type the parameters, because now they are implicitly any. + constructor(name, sellIn, quality) { + this.name = name; + this.sellIn = sellIn; + this.quality = quality; + } +} diff --git a/TypeScript/app/update-behaviors/index.ts b/TypeScript/app/update-behaviors/index.ts new file mode 100644 index 00000000..ec2e4546 --- /dev/null +++ b/TypeScript/app/update-behaviors/index.ts @@ -0,0 +1,2 @@ +export * from "./update-behavior.interface"; +export * from "./legacy-behavior"; diff --git a/TypeScript/app/update-behaviors/legacy-behavior.ts b/TypeScript/app/update-behaviors/legacy-behavior.ts new file mode 100644 index 00000000..e9fa304a --- /dev/null +++ b/TypeScript/app/update-behaviors/legacy-behavior.ts @@ -0,0 +1,56 @@ +import { Item } from "@app/item"; +import { IUpdateBehavior } from "./update-behavior.interface"; + +export class LegacyBehavior implements IUpdateBehavior { + constructor(private item: Item) {} + + update(): Item { + if ( + this.item.name !== "Aged Brie" && + this.item.name !== "Backstage passes to a TAFKAL80ETC concert" + ) { + if (this.item.quality > 0) { + if (this.item.name !== "Sulfuras, Hand of Ragnaros") { + this.item.quality = this.item.quality - 1; + } + } + } else { + if (this.item.quality < 50) { + this.item.quality = this.item.quality + 1; + if (this.item.name === "Backstage passes to a TAFKAL80ETC concert") { + if (this.item.sellIn < 11) { + if (this.item.quality < 50) { + this.item.quality = this.item.quality + 1; + } + } + if (this.item.sellIn < 6) { + if (this.item.quality < 50) { + this.item.quality = this.item.quality + 1; + } + } + } + } + } + if (this.item.name !== "Sulfuras, Hand of Ragnaros") { + this.item.sellIn = this.item.sellIn - 1; + } + if (this.item.sellIn < 0) { + if (this.item.name !== "Aged Brie") { + if (this.item.name !== "Backstage passes to a TAFKAL80ETC concert") { + if (this.item.quality > 0) { + if (this.item.name !== "Sulfuras, Hand of Ragnaros") { + this.item.quality = this.item.quality - 1; + } + } + } else { + this.item.quality = this.item.quality - this.item.quality; + } + } else { + if (this.item.quality < 50) { + this.item.quality = this.item.quality + 1; + } + } + } + return this.item; + } +} diff --git a/TypeScript/app/update-behaviors/update-behavior.interface.ts b/TypeScript/app/update-behaviors/update-behavior.interface.ts new file mode 100644 index 00000000..351156c1 --- /dev/null +++ b/TypeScript/app/update-behaviors/update-behavior.interface.ts @@ -0,0 +1,5 @@ +import { Item } from "@app/item"; + +export interface IUpdateBehavior { + update: () => Item; +} diff --git a/TypeScript/test/jest/approvals.spec.ts b/TypeScript/test/jest/approvals.spec.ts index ca6ba5d1..b33f47c0 100644 --- a/TypeScript/test/jest/approvals.spec.ts +++ b/TypeScript/test/jest/approvals.spec.ts @@ -1,4 +1,5 @@ -import { Item, GildedRose } from "@/gilded-rose"; +import { GildedRose } from "@app/gilded-rose"; +import { Item } from "@app/item"; describe("Gilded Rose Approval", () => { it("should match the snapshot for thirty Days", () => { diff --git a/TypeScript/test/jest/gilded-rose.spec.ts b/TypeScript/test/jest/gilded-rose.spec.ts index 391ee0c0..f28e1623 100644 --- a/TypeScript/test/jest/gilded-rose.spec.ts +++ b/TypeScript/test/jest/gilded-rose.spec.ts @@ -1,4 +1,5 @@ -import { Item, GildedRose } from "@/gilded-rose"; +import { GildedRose } from "@app/gilded-rose"; +import { Item } from "@app/item"; describe("Gilded Rose", () => { it("should have an empty array as items when no constructor parameter is provided", () => { diff --git a/TypeScript/tsconfig.json b/TypeScript/tsconfig.json index 1623eedf..eff4f0f1 100644 --- a/TypeScript/tsconfig.json +++ b/TypeScript/tsconfig.json @@ -9,7 +9,7 @@ "resolveJsonModule": true, "esModuleInterop": true, "paths": { - "@/*": ["app/*"] + "@app/*": ["app/*"] } }, "exclude": ["node_modules"] From 1563d86e903162504c1a4d1541800ac6b744eda7 Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 11:49:33 +0200 Subject: [PATCH 11/18] Move logic that maps item names to Update behavior to a new resolver file --- TypeScript/app/gilded-rose.ts | 11 ++--------- TypeScript/app/update-behaviors/behavior-resolver.ts | 10 ++++++++++ TypeScript/app/update-behaviors/index.ts | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 TypeScript/app/update-behaviors/behavior-resolver.ts diff --git a/TypeScript/app/gilded-rose.ts b/TypeScript/app/gilded-rose.ts index 2d894720..caee7ef2 100644 --- a/TypeScript/app/gilded-rose.ts +++ b/TypeScript/app/gilded-rose.ts @@ -1,5 +1,5 @@ import { Item } from "@app/item"; -import { IUpdateBehavior, LegacyBehavior } from "@app/update-behaviors"; +import { getUpdateBehaviorFor } from "@app/update-behaviors"; export class GildedRose { // also can't edit this because of the kata rules. @@ -12,14 +12,7 @@ export class GildedRose { updateQuality() { return this.items.map((item) => { - return this.#getUpdateBehaviorFor(item).update(); + return getUpdateBehaviorFor(item).update(); }); } - - #getUpdateBehaviorFor(item: Item): IUpdateBehavior { - switch (item.name) { - default: - return new LegacyBehavior(item); - } - } } diff --git a/TypeScript/app/update-behaviors/behavior-resolver.ts b/TypeScript/app/update-behaviors/behavior-resolver.ts new file mode 100644 index 00000000..5784f007 --- /dev/null +++ b/TypeScript/app/update-behaviors/behavior-resolver.ts @@ -0,0 +1,10 @@ +import { Item } from "@app/item"; +import { IUpdateBehavior } from "./update-behavior.interface"; +import { LegacyBehavior } from "./legacy-behavior"; + +export function getUpdateBehaviorFor(item: Item): IUpdateBehavior { + switch (item.name) { + default: + return new LegacyBehavior(item); + } +} diff --git a/TypeScript/app/update-behaviors/index.ts b/TypeScript/app/update-behaviors/index.ts index ec2e4546..bdaa4d9a 100644 --- a/TypeScript/app/update-behaviors/index.ts +++ b/TypeScript/app/update-behaviors/index.ts @@ -1,2 +1,2 @@ export * from "./update-behavior.interface"; -export * from "./legacy-behavior"; +export * from "./behavior-resolver"; From 372111147ffc0d6351e4def0b1090ca3dc938ef5 Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 11:50:17 +0200 Subject: [PATCH 12/18] Moved Aged Brie logic to AgedBrieBehavior --- .../app/update-behaviors/behavior-resolver.ts | 5 +++- .../implementations/aged-brie-behavior.ts | 23 +++++++++++++++++++ .../{ => implementations}/legacy-behavior.ts | 23 ++++++------------- 3 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 TypeScript/app/update-behaviors/implementations/aged-brie-behavior.ts rename TypeScript/app/update-behaviors/{ => implementations}/legacy-behavior.ts (61%) diff --git a/TypeScript/app/update-behaviors/behavior-resolver.ts b/TypeScript/app/update-behaviors/behavior-resolver.ts index 5784f007..93a99297 100644 --- a/TypeScript/app/update-behaviors/behavior-resolver.ts +++ b/TypeScript/app/update-behaviors/behavior-resolver.ts @@ -1,9 +1,12 @@ import { Item } from "@app/item"; import { IUpdateBehavior } from "./update-behavior.interface"; -import { LegacyBehavior } from "./legacy-behavior"; +import { LegacyBehavior } from "./implementations/legacy-behavior"; +import { AgedBrieBehavior } from "./implementations/aged-brie-behavior"; export function getUpdateBehaviorFor(item: Item): IUpdateBehavior { switch (item.name) { + case "Aged Brie": + return new AgedBrieBehavior(item); default: return new LegacyBehavior(item); } diff --git a/TypeScript/app/update-behaviors/implementations/aged-brie-behavior.ts b/TypeScript/app/update-behaviors/implementations/aged-brie-behavior.ts new file mode 100644 index 00000000..a21e1a50 --- /dev/null +++ b/TypeScript/app/update-behaviors/implementations/aged-brie-behavior.ts @@ -0,0 +1,23 @@ +import { Item } from "@app/item"; +import { IUpdateBehavior } from "../update-behavior.interface"; + +export class AgedBrieBehavior implements IUpdateBehavior { + readonly #MAX_AMOUNT = 50; + + constructor(private item: Item) {} + + update(): Item { + this.item.sellIn -= 1; + + const isPastSellInDay = this.item.sellIn < 0; + + const amountToAdd = isPastSellInDay ? 2 : 1; + + this.item.quality = Math.min( + this.#MAX_AMOUNT, + this.item.quality + amountToAdd + ); + + return this.item; + } +} diff --git a/TypeScript/app/update-behaviors/legacy-behavior.ts b/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts similarity index 61% rename from TypeScript/app/update-behaviors/legacy-behavior.ts rename to TypeScript/app/update-behaviors/implementations/legacy-behavior.ts index e9fa304a..5d140fee 100644 --- a/TypeScript/app/update-behaviors/legacy-behavior.ts +++ b/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts @@ -1,14 +1,11 @@ import { Item } from "@app/item"; -import { IUpdateBehavior } from "./update-behavior.interface"; +import { IUpdateBehavior } from "../update-behavior.interface"; export class LegacyBehavior implements IUpdateBehavior { constructor(private item: Item) {} update(): Item { - if ( - this.item.name !== "Aged Brie" && - this.item.name !== "Backstage passes to a TAFKAL80ETC concert" - ) { + if (this.item.name !== "Backstage passes to a TAFKAL80ETC concert") { if (this.item.quality > 0) { if (this.item.name !== "Sulfuras, Hand of Ragnaros") { this.item.quality = this.item.quality - 1; @@ -35,20 +32,14 @@ export class LegacyBehavior implements IUpdateBehavior { this.item.sellIn = this.item.sellIn - 1; } if (this.item.sellIn < 0) { - if (this.item.name !== "Aged Brie") { - if (this.item.name !== "Backstage passes to a TAFKAL80ETC concert") { - if (this.item.quality > 0) { - if (this.item.name !== "Sulfuras, Hand of Ragnaros") { - this.item.quality = this.item.quality - 1; - } + if (this.item.name !== "Backstage passes to a TAFKAL80ETC concert") { + if (this.item.quality > 0) { + if (this.item.name !== "Sulfuras, Hand of Ragnaros") { + this.item.quality = this.item.quality - 1; } - } else { - this.item.quality = this.item.quality - this.item.quality; } } else { - if (this.item.quality < 50) { - this.item.quality = this.item.quality + 1; - } + this.item.quality = this.item.quality - this.item.quality; } } return this.item; From cc8a2b35b00518a8efdde9ee5321443591b0fa7c Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 12:56:19 +0200 Subject: [PATCH 13/18] Refactor tests - moved snapshot tests to better named folder - refactored gilded-rose tests to unit tests in their right location. - mock implementation details in gilded rose test - wrote unit tests for behavior resolver - fix testing config to account for the changes --- TypeScript/app/gilded-rose.spec.ts | 40 +++++ .../behavior-resolver.spec.ts | 18 +++ .../aged-brie-behavior.spec.ts | 40 +++++ .../implementations/legacy-behavior.spec.ts | 110 +++++++++++++ TypeScript/jest.config.ts | 16 +- TypeScript/package.json | 4 +- .../__snapshots__/approvals.spec.ts.snap | 0 .../jest => snapshot-tests}/approvals.spec.ts | 0 TypeScript/test/jest/gilded-rose.spec.ts | 153 ------------------ 9 files changed, 219 insertions(+), 162 deletions(-) create mode 100644 TypeScript/app/gilded-rose.spec.ts create mode 100644 TypeScript/app/update-behaviors/behavior-resolver.spec.ts create mode 100644 TypeScript/app/update-behaviors/implementations/aged-brie-behavior.spec.ts create mode 100644 TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts rename TypeScript/{test/jest => snapshot-tests}/__snapshots__/approvals.spec.ts.snap (100%) rename TypeScript/{test/jest => snapshot-tests}/approvals.spec.ts (100%) delete mode 100644 TypeScript/test/jest/gilded-rose.spec.ts diff --git a/TypeScript/app/gilded-rose.spec.ts b/TypeScript/app/gilded-rose.spec.ts new file mode 100644 index 00000000..e88057e0 --- /dev/null +++ b/TypeScript/app/gilded-rose.spec.ts @@ -0,0 +1,40 @@ +const getUpdateBehaviorMock = jest.fn((item: Item) => new MockBehavior(item)); +const updateMock = jest.fn((item: Item) => item); + +jest.mock("@app/update-behaviors", () => ({ + getUpdateBehaviorFor: getUpdateBehaviorMock, +})); + +import { GildedRose } from "@app/gilded-rose"; +import { Item } from "@app/item"; +import { IUpdateBehavior } from "@app/update-behaviors"; + +export class MockBehavior implements IUpdateBehavior { + constructor(public item: Item) {} + update() { + return updateMock(this.item); + } +} + +describe("Gilded Rose", () => { + it("should have an empty array as items when no constructor parameter is provided", () => { + const gildedRose = new GildedRose(); + expect(gildedRose.items).toEqual([]); + }); + + it("should call the behavior resolver and update function for each item", () => { + const item1 = new Item("item 1", 0, 0); + const item2 = new Item("item 2", 0, 0); + const gildedRose = new GildedRose([item1, item2]); + + gildedRose.updateQuality(); + + expect(getUpdateBehaviorMock).toHaveBeenCalledWith(item1); + expect(getUpdateBehaviorMock).toHaveBeenCalledWith(item2); + + expect(updateMock).toHaveBeenCalledWith(item1); + expect(updateMock).toHaveBeenCalledWith(item2); + }); + + // to implement: "Conjured" items degrade in Quality twice as fast as normal items +}); diff --git a/TypeScript/app/update-behaviors/behavior-resolver.spec.ts b/TypeScript/app/update-behaviors/behavior-resolver.spec.ts new file mode 100644 index 00000000..97f0ce0c --- /dev/null +++ b/TypeScript/app/update-behaviors/behavior-resolver.spec.ts @@ -0,0 +1,18 @@ +import { Item } from "@app/item"; +import { getUpdateBehaviorFor } from "./behavior-resolver"; +import { AgedBrieBehavior } from "./implementations/aged-brie-behavior"; +import { LegacyBehavior } from "./implementations/legacy-behavior"; + +describe("Behavior resolver", () => { + it("should correctly resolve Aged Brie", () => { + expect(getUpdateBehaviorFor(new Item("Aged Brie", 0, 0))).toBeInstanceOf( + AgedBrieBehavior + ); + }); + + it("should correctly resolve the rest to Legacy behavior", () => { + expect( + getUpdateBehaviorFor(new Item("some other item", 0, 0)) + ).toBeInstanceOf(LegacyBehavior); + }); +}); diff --git a/TypeScript/app/update-behaviors/implementations/aged-brie-behavior.spec.ts b/TypeScript/app/update-behaviors/implementations/aged-brie-behavior.spec.ts new file mode 100644 index 00000000..2094db81 --- /dev/null +++ b/TypeScript/app/update-behaviors/implementations/aged-brie-behavior.spec.ts @@ -0,0 +1,40 @@ +import { Item } from "@app/item"; +import { AgedBrieBehavior } from "./aged-brie-behavior"; + +describe("AgedBrie Behavior", () => { + it("should increase quality of Aged Brie as it gets older", () => { + const behavior = new AgedBrieBehavior(new Item("Aged Brie", 1, 1)); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Aged Brie", + sellIn: 0, + quality: 2, + }); + }); + + it("should increase quality of Aged Brie twice as fast after sell in date", () => { + const behavior = new AgedBrieBehavior(new Item("Aged Brie", 0, 1)); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Aged Brie", + sellIn: -1, + quality: 3, + }); + }); + + it("should never increase quality of Aged Brie over 50", () => { + const behavior = new AgedBrieBehavior(new Item("Aged Brie", 0, 50)); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Aged Brie", + sellIn: -1, + quality: 50, + }); + }); +}); diff --git a/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts b/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts new file mode 100644 index 00000000..c7d86904 --- /dev/null +++ b/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts @@ -0,0 +1,110 @@ +import { Item } from "@app/item"; +import { LegacyBehavior } from "./legacy-behavior"; + +describe("Legacy Behavior", () => { + it("should degrade sell inn and quality each day", () => { + const behavior = new LegacyBehavior(new Item("standard item", 1, 1)); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "standard item", + sellIn: 0, + quality: 0, + }); + }); + + it("should degrade quality twice as fast after sell in date", () => { + const behavior = new LegacyBehavior(new Item("standard item", 0, 2)); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "standard item", + sellIn: -1, + quality: 0, + }); + }); + + it("should not degrade quality below 0", () => { + const behavior = new LegacyBehavior(new Item("standard item", 1, 0)); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "standard item", + sellIn: 0, + quality: 0, + }); + }); + + it("should not change quality of Sulfuras", () => { + const behavior = new LegacyBehavior( + new Item("Sulfuras, Hand of Ragnaros", 0, 80) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Sulfuras, Hand of Ragnaros", + sellIn: 0, + quality: 80, + }); + }); + + it("should increase quality of Backstage passes by 1 if sell in date is more than 10 days away", () => { + const behavior = new LegacyBehavior( + new Item("Backstage passes to a TAFKAL80ETC concert", 11, 20) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: 10, + quality: 21, + }); + }); + + it("should increase quality of Backstage passes by 2 if sell in date is less than 10 days away but more than 5", () => { + const behavior = new LegacyBehavior( + new Item("Backstage passes to a TAFKAL80ETC concert", 9, 20) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: 8, + quality: 22, + }); + }); + + it("should increase quality of Backstage passes by 3 if sell in date is less than 5 days away", () => { + const behavior = new LegacyBehavior( + new Item("Backstage passes to a TAFKAL80ETC concert", 4, 20) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: 3, + quality: 23, + }); + }); + + it("should drop quality of Backstage passes to 0 after sell in date", () => { + const behavior = new LegacyBehavior( + new Item("Backstage passes to a TAFKAL80ETC concert", 0, 20) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: -1, + quality: 0, + }); + }); +}); diff --git a/TypeScript/jest.config.ts b/TypeScript/jest.config.ts index caffa5b4..4b8cecb3 100644 --- a/TypeScript/jest.config.ts +++ b/TypeScript/jest.config.ts @@ -1,13 +1,15 @@ -import { pathsToModuleNameMapper } from "ts-jest"; -import { compilerOptions } from './tsconfig.json' +import { pathsToModuleNameMapper } from "ts-jest"; +import { compilerOptions } from "./tsconfig.json"; export default { - roots: ['/app', '/test/jest'], + roots: ["/app", "/snapshot-tests"], collectCoverage: true, - coverageDirectory: 'coverage', - coverageProvider: 'v8', + coverageDirectory: "coverage", + coverageProvider: "v8", transform: { - '^.+\\.tsx?$': 'ts-jest', + "^.+\\.tsx?$": "ts-jest", }, - moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '/' } ), + moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { + prefix: "/", + }), }; diff --git a/TypeScript/package.json b/TypeScript/package.json index b5e53907..0a0c6e57 100644 --- a/TypeScript/package.json +++ b/TypeScript/package.json @@ -3,9 +3,9 @@ "version": "1.0.0", "description": "Gilded Rose kata in TypeScript", "scripts": { - "precompile": "rimraf app/**/*.js test/**/*.js", + "precompile": "rimraf app/**/*.js snapshot-tests/**/*.js", "compile": "tsc", - "pretest": "rimraf app/**/*.js test/**/*.js", + "pretest": "rimraf app/**/*.js snapshot-tests/**/*.js", "test": "jest", "test:watch": "jest --watchAll" }, diff --git a/TypeScript/test/jest/__snapshots__/approvals.spec.ts.snap b/TypeScript/snapshot-tests/__snapshots__/approvals.spec.ts.snap similarity index 100% rename from TypeScript/test/jest/__snapshots__/approvals.spec.ts.snap rename to TypeScript/snapshot-tests/__snapshots__/approvals.spec.ts.snap diff --git a/TypeScript/test/jest/approvals.spec.ts b/TypeScript/snapshot-tests/approvals.spec.ts similarity index 100% rename from TypeScript/test/jest/approvals.spec.ts rename to TypeScript/snapshot-tests/approvals.spec.ts diff --git a/TypeScript/test/jest/gilded-rose.spec.ts b/TypeScript/test/jest/gilded-rose.spec.ts deleted file mode 100644 index f28e1623..00000000 --- a/TypeScript/test/jest/gilded-rose.spec.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { GildedRose } from "@app/gilded-rose"; -import { Item } from "@app/item"; - -describe("Gilded Rose", () => { - it("should have an empty array as items when no constructor parameter is provided", () => { - const gildedRose = new GildedRose(); - expect(gildedRose.items).toEqual([]); - }); - - it("should degrade sell inn and quality each day", () => { - const gildedRose = new GildedRose([new Item("standard item", 1, 1)]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "standard item", - sellIn: 0, - quality: 0, - }); - }); - - it("should degrade quality twice as fast after sell in date", () => { - const gildedRose = new GildedRose([new Item("standard item", 0, 2)]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "standard item", - sellIn: -1, - quality: 0, - }); - }); - - it("should not degrade quality below 0", () => { - const gildedRose = new GildedRose([new Item("standard item", 1, 0)]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "standard item", - sellIn: 0, - quality: 0, - }); - }); - - it("should increase quality of Aged Brie as it gets older", () => { - const gildedRose = new GildedRose([new Item("Aged Brie", 1, 1)]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "Aged Brie", - sellIn: 0, - quality: 2, - }); - }); - - it("should increase quality of Aged Brie twice as fast after sell in date", () => { - const gildedRose = new GildedRose([new Item("Aged Brie", 0, 1)]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "Aged Brie", - sellIn: -1, - quality: 3, - }); - }); - - it("should never increase quality of Aged Brie over 50", () => { - const gildedRose = new GildedRose([new Item("Aged Brie", 0, 50)]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "Aged Brie", - sellIn: -1, - quality: 50, - }); - }); - - it("should not change quality of Sulfuras", () => { - const gildedRose = new GildedRose([ - new Item("Sulfuras, Hand of Ragnaros", 0, 80), - ]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "Sulfuras, Hand of Ragnaros", - sellIn: 0, - quality: 80, - }); - }); - - it("should increase quality of Backstage passes by 1 if sell in date is more than 10 days away", () => { - const gildedRose = new GildedRose([ - new Item("Backstage passes to a TAFKAL80ETC concert", 11, 20), - ]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "Backstage passes to a TAFKAL80ETC concert", - sellIn: 10, - quality: 21, - }); - }); - - it("should increase quality of Backstage passes by 2 if sell in date is less than 10 days away but more than 5", () => { - const gildedRose = new GildedRose([ - new Item("Backstage passes to a TAFKAL80ETC concert", 9, 20), - ]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "Backstage passes to a TAFKAL80ETC concert", - sellIn: 8, - quality: 22, - }); - }); - - it("should increase quality of Backstage passes by 3 if sell in date is less than 5 days away", () => { - const gildedRose = new GildedRose([ - new Item("Backstage passes to a TAFKAL80ETC concert", 4, 20), - ]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "Backstage passes to a TAFKAL80ETC concert", - sellIn: 3, - quality: 23, - }); - }); - - it("should drop quality of Backstage passes to 0 after sell in date", () => { - const gildedRose = new GildedRose([ - new Item("Backstage passes to a TAFKAL80ETC concert", 0, 20), - ]); - - const items = gildedRose.updateQuality(); - - expect(items[0]).toMatchObject({ - name: "Backstage passes to a TAFKAL80ETC concert", - sellIn: -1, - quality: 0, - }); - }); - - // to implement: "Conjured" items degrade in Quality twice as fast as normal items -}); From ef9c478e7ccbcb48e3ffd2a7e51e3955f03419a5 Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 15:08:07 +0200 Subject: [PATCH 14/18] Move backstage pass logic to backstage-pass-behavior --- TypeScript/app/config.ts | 3 + .../behavior-resolver.spec.ts | 11 ++- .../app/update-behaviors/behavior-resolver.ts | 5 +- .../backstage-pass-behavior.spec.ts | 74 +++++++++++++++++++ .../backstage-pass/backstage-pass-behavior.ts | 30 ++++++++ .../implementations/legacy-behavior.spec.ts | 56 -------------- .../implementations/legacy-behavior.ts | 41 +++------- 7 files changed, 131 insertions(+), 89 deletions(-) create mode 100644 TypeScript/app/config.ts create mode 100644 TypeScript/app/update-behaviors/implementations/backstage-pass/backstage-pass-behavior.spec.ts create mode 100644 TypeScript/app/update-behaviors/implementations/backstage-pass/backstage-pass-behavior.ts diff --git a/TypeScript/app/config.ts b/TypeScript/app/config.ts new file mode 100644 index 00000000..44cedf34 --- /dev/null +++ b/TypeScript/app/config.ts @@ -0,0 +1,3 @@ +export const config = { + maxQuality: 50, +}; diff --git a/TypeScript/app/update-behaviors/behavior-resolver.spec.ts b/TypeScript/app/update-behaviors/behavior-resolver.spec.ts index 97f0ce0c..80c923bd 100644 --- a/TypeScript/app/update-behaviors/behavior-resolver.spec.ts +++ b/TypeScript/app/update-behaviors/behavior-resolver.spec.ts @@ -1,7 +1,8 @@ import { Item } from "@app/item"; import { getUpdateBehaviorFor } from "./behavior-resolver"; -import { AgedBrieBehavior } from "./implementations/aged-brie-behavior"; +import { AgedBrieBehavior } from "./implementations/aged-brie/aged-brie-behavior"; import { LegacyBehavior } from "./implementations/legacy-behavior"; +import { BackstagePassBehavior } from "./implementations/backstage-pass/backstage-pass-behavior"; describe("Behavior resolver", () => { it("should correctly resolve Aged Brie", () => { @@ -10,6 +11,14 @@ describe("Behavior resolver", () => { ); }); + it("should correctly resolve Backstage Passes", () => { + expect( + getUpdateBehaviorFor( + new Item("Backstage passes to a TAFKAL80ETC concert", 0, 0) + ) + ).toBeInstanceOf(BackstagePassBehavior); + }); + it("should correctly resolve the rest to Legacy behavior", () => { expect( getUpdateBehaviorFor(new Item("some other item", 0, 0)) diff --git a/TypeScript/app/update-behaviors/behavior-resolver.ts b/TypeScript/app/update-behaviors/behavior-resolver.ts index 93a99297..c0e179fb 100644 --- a/TypeScript/app/update-behaviors/behavior-resolver.ts +++ b/TypeScript/app/update-behaviors/behavior-resolver.ts @@ -1,12 +1,15 @@ import { Item } from "@app/item"; import { IUpdateBehavior } from "./update-behavior.interface"; import { LegacyBehavior } from "./implementations/legacy-behavior"; -import { AgedBrieBehavior } from "./implementations/aged-brie-behavior"; +import { AgedBrieBehavior } from "./implementations/aged-brie/aged-brie-behavior"; +import { BackstagePassBehavior } from "./implementations/backstage-pass/backstage-pass-behavior"; export function getUpdateBehaviorFor(item: Item): IUpdateBehavior { switch (item.name) { case "Aged Brie": return new AgedBrieBehavior(item); + case "Backstage passes to a TAFKAL80ETC concert": + return new BackstagePassBehavior(item); default: return new LegacyBehavior(item); } diff --git a/TypeScript/app/update-behaviors/implementations/backstage-pass/backstage-pass-behavior.spec.ts b/TypeScript/app/update-behaviors/implementations/backstage-pass/backstage-pass-behavior.spec.ts new file mode 100644 index 00000000..aca4dbff --- /dev/null +++ b/TypeScript/app/update-behaviors/implementations/backstage-pass/backstage-pass-behavior.spec.ts @@ -0,0 +1,74 @@ +import { Item } from "@app/item"; +import { BackstagePassBehavior } from "./backstage-pass-behavior"; + +describe("Backstage Pass Behavior", () => { + it("should increase quality of Backstage passes by 1 if sell in date is more than 10 days away", () => { + const behavior = new BackstagePassBehavior( + new Item("Backstage passes to a TAFKAL80ETC concert", 11, 20) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: 10, + quality: 21, + }); + }); + + it("should increase quality of Backstage passes by 2 if sell in date is less than 10 days away but more than 5", () => { + const behavior = new BackstagePassBehavior( + new Item("Backstage passes to a TAFKAL80ETC concert", 9, 20) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: 8, + quality: 22, + }); + }); + + it("should increase quality of Backstage passes by 3 if sell in date is less than 5 days away", () => { + const behavior = new BackstagePassBehavior( + new Item("Backstage passes to a TAFKAL80ETC concert", 4, 20) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: 3, + quality: 23, + }); + }); + + it("should drop quality of Backstage passes to 0 after sell in date", () => { + const behavior = new BackstagePassBehavior( + new Item("Backstage passes to a TAFKAL80ETC concert", 0, 20) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: -1, + quality: 0, + }); + }); + + it("should not increase over 50", () => { + const behavior = new BackstagePassBehavior( + new Item("Backstage passes to a TAFKAL80ETC concert", 4, 50) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Backstage passes to a TAFKAL80ETC concert", + sellIn: 3, + quality: 50, + }); + }); +}); diff --git a/TypeScript/app/update-behaviors/implementations/backstage-pass/backstage-pass-behavior.ts b/TypeScript/app/update-behaviors/implementations/backstage-pass/backstage-pass-behavior.ts new file mode 100644 index 00000000..c0996df4 --- /dev/null +++ b/TypeScript/app/update-behaviors/implementations/backstage-pass/backstage-pass-behavior.ts @@ -0,0 +1,30 @@ +import { config } from "@app/config"; +import { Item } from "@app/item"; +import { IUpdateBehavior } from "@app/update-behaviors"; + +export class BackstagePassBehavior implements IUpdateBehavior { + constructor(private item: Item) {} + update(): Item { + const sellIn = this.item.sellIn; + const amountToIncrease = this.#getAmountToIncrease(sellIn); + + this.item.quality = Math.min( + this.item.quality + amountToIncrease, + config.maxQuality + ); + + if (sellIn <= 0) { + this.item.quality = 0; + } + + this.item.sellIn -= 1; + + return this.item; + } + + #getAmountToIncrease(sellIn: number): number { + if (sellIn <= 5) return 3; + if (sellIn <= 10) return 2; + return 1; + } +} diff --git a/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts b/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts index c7d86904..9bff7c86 100644 --- a/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts +++ b/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts @@ -51,60 +51,4 @@ describe("Legacy Behavior", () => { quality: 80, }); }); - - it("should increase quality of Backstage passes by 1 if sell in date is more than 10 days away", () => { - const behavior = new LegacyBehavior( - new Item("Backstage passes to a TAFKAL80ETC concert", 11, 20) - ); - - const result = behavior.update(); - - expect(result).toMatchObject({ - name: "Backstage passes to a TAFKAL80ETC concert", - sellIn: 10, - quality: 21, - }); - }); - - it("should increase quality of Backstage passes by 2 if sell in date is less than 10 days away but more than 5", () => { - const behavior = new LegacyBehavior( - new Item("Backstage passes to a TAFKAL80ETC concert", 9, 20) - ); - - const result = behavior.update(); - - expect(result).toMatchObject({ - name: "Backstage passes to a TAFKAL80ETC concert", - sellIn: 8, - quality: 22, - }); - }); - - it("should increase quality of Backstage passes by 3 if sell in date is less than 5 days away", () => { - const behavior = new LegacyBehavior( - new Item("Backstage passes to a TAFKAL80ETC concert", 4, 20) - ); - - const result = behavior.update(); - - expect(result).toMatchObject({ - name: "Backstage passes to a TAFKAL80ETC concert", - sellIn: 3, - quality: 23, - }); - }); - - it("should drop quality of Backstage passes to 0 after sell in date", () => { - const behavior = new LegacyBehavior( - new Item("Backstage passes to a TAFKAL80ETC concert", 0, 20) - ); - - const result = behavior.update(); - - expect(result).toMatchObject({ - name: "Backstage passes to a TAFKAL80ETC concert", - sellIn: -1, - quality: 0, - }); - }); }); diff --git a/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts b/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts index 5d140fee..c04b8e73 100644 --- a/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts +++ b/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts @@ -5,42 +5,21 @@ export class LegacyBehavior implements IUpdateBehavior { constructor(private item: Item) {} update(): Item { - if (this.item.name !== "Backstage passes to a TAFKAL80ETC concert") { + if (this.item.quality > 0) { + if (this.item.name !== "Sulfuras, Hand of Ragnaros") { + this.item.quality = this.item.quality - 1; + } + } + + if (this.item.name !== "Sulfuras, Hand of Ragnaros") { + this.item.sellIn = this.item.sellIn - 1; + } + if (this.item.sellIn < 0) { if (this.item.quality > 0) { if (this.item.name !== "Sulfuras, Hand of Ragnaros") { this.item.quality = this.item.quality - 1; } } - } else { - if (this.item.quality < 50) { - this.item.quality = this.item.quality + 1; - if (this.item.name === "Backstage passes to a TAFKAL80ETC concert") { - if (this.item.sellIn < 11) { - if (this.item.quality < 50) { - this.item.quality = this.item.quality + 1; - } - } - if (this.item.sellIn < 6) { - if (this.item.quality < 50) { - this.item.quality = this.item.quality + 1; - } - } - } - } - } - if (this.item.name !== "Sulfuras, Hand of Ragnaros") { - this.item.sellIn = this.item.sellIn - 1; - } - if (this.item.sellIn < 0) { - if (this.item.name !== "Backstage passes to a TAFKAL80ETC concert") { - if (this.item.quality > 0) { - if (this.item.name !== "Sulfuras, Hand of Ragnaros") { - this.item.quality = this.item.quality - 1; - } - } - } else { - this.item.quality = this.item.quality - this.item.quality; - } } return this.item; } From 6c4e56174ce13bb641016fcadc4b16e28add764b Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 15:08:37 +0200 Subject: [PATCH 15/18] Moved aged-brie-behavior to its own folder, use config.maxQuality --- .../{ => aged-brie}/aged-brie-behavior.spec.ts | 0 .../{ => aged-brie}/aged-brie-behavior.ts | 13 ++++++------- 2 files changed, 6 insertions(+), 7 deletions(-) rename TypeScript/app/update-behaviors/implementations/{ => aged-brie}/aged-brie-behavior.spec.ts (100%) rename TypeScript/app/update-behaviors/implementations/{ => aged-brie}/aged-brie-behavior.ts (66%) diff --git a/TypeScript/app/update-behaviors/implementations/aged-brie-behavior.spec.ts b/TypeScript/app/update-behaviors/implementations/aged-brie/aged-brie-behavior.spec.ts similarity index 100% rename from TypeScript/app/update-behaviors/implementations/aged-brie-behavior.spec.ts rename to TypeScript/app/update-behaviors/implementations/aged-brie/aged-brie-behavior.spec.ts diff --git a/TypeScript/app/update-behaviors/implementations/aged-brie-behavior.ts b/TypeScript/app/update-behaviors/implementations/aged-brie/aged-brie-behavior.ts similarity index 66% rename from TypeScript/app/update-behaviors/implementations/aged-brie-behavior.ts rename to TypeScript/app/update-behaviors/implementations/aged-brie/aged-brie-behavior.ts index a21e1a50..9e3ca51e 100644 --- a/TypeScript/app/update-behaviors/implementations/aged-brie-behavior.ts +++ b/TypeScript/app/update-behaviors/implementations/aged-brie/aged-brie-behavior.ts @@ -1,23 +1,22 @@ +import { config } from "@app/config"; import { Item } from "@app/item"; -import { IUpdateBehavior } from "../update-behavior.interface"; +import { IUpdateBehavior } from "@app/update-behaviors"; export class AgedBrieBehavior implements IUpdateBehavior { - readonly #MAX_AMOUNT = 50; - constructor(private item: Item) {} update(): Item { - this.item.sellIn -= 1; - - const isPastSellInDay = this.item.sellIn < 0; + const isPastSellInDay = this.item.sellIn <= 0; const amountToAdd = isPastSellInDay ? 2 : 1; this.item.quality = Math.min( - this.#MAX_AMOUNT, + config.maxQuality, this.item.quality + amountToAdd ); + this.item.sellIn -= 1; + return this.item; } } From 09e9ec6e179bc1c212d03c40d67c85baa3f9c068 Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 15:39:19 +0200 Subject: [PATCH 16/18] Move Legendary item logic to legendary-item-behavior --- .../behavior-resolver.spec.ts | 7 +++++ .../app/update-behaviors/behavior-resolver.ts | 3 ++ .../implementations/legacy-behavior.spec.ts | 14 ---------- .../implementations/legacy-behavior.ts | 12 ++------ .../legendary-item/legendary-item-behavior.ts | 13 +++++++++ .../legendary-item/legendary-item.spec.ts | 28 +++++++++++++++++++ 6 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 TypeScript/app/update-behaviors/implementations/legendary-item/legendary-item-behavior.ts create mode 100644 TypeScript/app/update-behaviors/implementations/legendary-item/legendary-item.spec.ts diff --git a/TypeScript/app/update-behaviors/behavior-resolver.spec.ts b/TypeScript/app/update-behaviors/behavior-resolver.spec.ts index 80c923bd..50b2729b 100644 --- a/TypeScript/app/update-behaviors/behavior-resolver.spec.ts +++ b/TypeScript/app/update-behaviors/behavior-resolver.spec.ts @@ -3,6 +3,7 @@ import { getUpdateBehaviorFor } from "./behavior-resolver"; import { AgedBrieBehavior } from "./implementations/aged-brie/aged-brie-behavior"; import { LegacyBehavior } from "./implementations/legacy-behavior"; import { BackstagePassBehavior } from "./implementations/backstage-pass/backstage-pass-behavior"; +import { LegendaryItemBehavior } from "./implementations/legendary-item/legendary-item-behavior"; describe("Behavior resolver", () => { it("should correctly resolve Aged Brie", () => { @@ -19,6 +20,12 @@ describe("Behavior resolver", () => { ).toBeInstanceOf(BackstagePassBehavior); }); + it("should correctly resolve Legendary Items", () => { + expect( + getUpdateBehaviorFor(new Item("Sulfuras, Hand of Ragnaros", 0, 0)) + ).toBeInstanceOf(LegendaryItemBehavior); + }); + it("should correctly resolve the rest to Legacy behavior", () => { expect( getUpdateBehaviorFor(new Item("some other item", 0, 0)) diff --git a/TypeScript/app/update-behaviors/behavior-resolver.ts b/TypeScript/app/update-behaviors/behavior-resolver.ts index c0e179fb..47a240c0 100644 --- a/TypeScript/app/update-behaviors/behavior-resolver.ts +++ b/TypeScript/app/update-behaviors/behavior-resolver.ts @@ -3,6 +3,7 @@ import { IUpdateBehavior } from "./update-behavior.interface"; import { LegacyBehavior } from "./implementations/legacy-behavior"; import { AgedBrieBehavior } from "./implementations/aged-brie/aged-brie-behavior"; import { BackstagePassBehavior } from "./implementations/backstage-pass/backstage-pass-behavior"; +import { LegendaryItemBehavior } from "./implementations/legendary-item/legendary-item-behavior"; export function getUpdateBehaviorFor(item: Item): IUpdateBehavior { switch (item.name) { @@ -10,6 +11,8 @@ export function getUpdateBehaviorFor(item: Item): IUpdateBehavior { return new AgedBrieBehavior(item); case "Backstage passes to a TAFKAL80ETC concert": return new BackstagePassBehavior(item); + case "Sulfuras, Hand of Ragnaros": + return new LegendaryItemBehavior(item); default: return new LegacyBehavior(item); } diff --git a/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts b/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts index 9bff7c86..0458c851 100644 --- a/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts +++ b/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts @@ -37,18 +37,4 @@ describe("Legacy Behavior", () => { quality: 0, }); }); - - it("should not change quality of Sulfuras", () => { - const behavior = new LegacyBehavior( - new Item("Sulfuras, Hand of Ragnaros", 0, 80) - ); - - const result = behavior.update(); - - expect(result).toMatchObject({ - name: "Sulfuras, Hand of Ragnaros", - sellIn: 0, - quality: 80, - }); - }); }); diff --git a/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts b/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts index c04b8e73..9bf81114 100644 --- a/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts +++ b/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts @@ -6,19 +6,13 @@ export class LegacyBehavior implements IUpdateBehavior { update(): Item { if (this.item.quality > 0) { - if (this.item.name !== "Sulfuras, Hand of Ragnaros") { - this.item.quality = this.item.quality - 1; - } + this.item.quality = this.item.quality - 1; } - if (this.item.name !== "Sulfuras, Hand of Ragnaros") { - this.item.sellIn = this.item.sellIn - 1; - } + this.item.sellIn = this.item.sellIn - 1; if (this.item.sellIn < 0) { if (this.item.quality > 0) { - if (this.item.name !== "Sulfuras, Hand of Ragnaros") { - this.item.quality = this.item.quality - 1; - } + this.item.quality = this.item.quality - 1; } } return this.item; diff --git a/TypeScript/app/update-behaviors/implementations/legendary-item/legendary-item-behavior.ts b/TypeScript/app/update-behaviors/implementations/legendary-item/legendary-item-behavior.ts new file mode 100644 index 00000000..cfe2750c --- /dev/null +++ b/TypeScript/app/update-behaviors/implementations/legendary-item/legendary-item-behavior.ts @@ -0,0 +1,13 @@ +import { config } from "@app/config"; +import { Item } from "@app/item"; +import { IUpdateBehavior } from "@app/update-behaviors/update-behavior.interface"; + +export class LegendaryItemBehavior implements IUpdateBehavior { + constructor(public item: Item) {} + update(): Item { + if (this.item.quality !== 80) { + throw new Error("A Legendary Item cannot have a quality other than 80"); + } + return this.item; + } +} diff --git a/TypeScript/app/update-behaviors/implementations/legendary-item/legendary-item.spec.ts b/TypeScript/app/update-behaviors/implementations/legendary-item/legendary-item.spec.ts new file mode 100644 index 00000000..f3fef643 --- /dev/null +++ b/TypeScript/app/update-behaviors/implementations/legendary-item/legendary-item.spec.ts @@ -0,0 +1,28 @@ +import { Item } from "@app/item"; +import { LegendaryItemBehavior } from "./legendary-item-behavior"; + +describe("Legendary Item Behavior", () => { + it("should keep items of quality 80 the same", () => { + const behavior = new LegendaryItemBehavior( + new Item("Sulfuras, Hand of Ragnaros", 0, 80) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Sulfuras, Hand of Ragnaros", + sellIn: 0, + quality: 80, + }); + }); + + it("should throw an error when a legendary item doesn't have a quality of 80", () => { + const behavior = new LegendaryItemBehavior( + new Item("Sulfuras, Hand of Ragnaros", 0, 5) + ); + + expect(() => behavior.update()).toThrow( + "A Legendary Item cannot have a quality other than 80" + ); + }); +}); From 3fcea88b62e4a8c009d3b897731fde482b2a1903 Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 15:52:53 +0200 Subject: [PATCH 17/18] Renamed LegacyBehavior to DefaultBehavior and refactored it's logic --- .../behavior-resolver.spec.ts | 4 ++-- .../app/update-behaviors/behavior-resolver.ts | 4 ++-- .../default-behavior.spec.ts} | 10 +++++----- .../default/default-behavior.ts | 15 ++++++++++++++ .../implementations/legacy-behavior.ts | 20 ------------------- 5 files changed, 24 insertions(+), 29 deletions(-) rename TypeScript/app/update-behaviors/implementations/{legacy-behavior.spec.ts => default/default-behavior.spec.ts} (68%) create mode 100644 TypeScript/app/update-behaviors/implementations/default/default-behavior.ts delete mode 100644 TypeScript/app/update-behaviors/implementations/legacy-behavior.ts diff --git a/TypeScript/app/update-behaviors/behavior-resolver.spec.ts b/TypeScript/app/update-behaviors/behavior-resolver.spec.ts index 50b2729b..03af7f34 100644 --- a/TypeScript/app/update-behaviors/behavior-resolver.spec.ts +++ b/TypeScript/app/update-behaviors/behavior-resolver.spec.ts @@ -1,7 +1,7 @@ import { Item } from "@app/item"; import { getUpdateBehaviorFor } from "./behavior-resolver"; import { AgedBrieBehavior } from "./implementations/aged-brie/aged-brie-behavior"; -import { LegacyBehavior } from "./implementations/legacy-behavior"; +import { DefaultBehavior } from "./implementations/default/default-behavior"; import { BackstagePassBehavior } from "./implementations/backstage-pass/backstage-pass-behavior"; import { LegendaryItemBehavior } from "./implementations/legendary-item/legendary-item-behavior"; @@ -29,6 +29,6 @@ describe("Behavior resolver", () => { it("should correctly resolve the rest to Legacy behavior", () => { expect( getUpdateBehaviorFor(new Item("some other item", 0, 0)) - ).toBeInstanceOf(LegacyBehavior); + ).toBeInstanceOf(DefaultBehavior); }); }); diff --git a/TypeScript/app/update-behaviors/behavior-resolver.ts b/TypeScript/app/update-behaviors/behavior-resolver.ts index 47a240c0..ff856b10 100644 --- a/TypeScript/app/update-behaviors/behavior-resolver.ts +++ b/TypeScript/app/update-behaviors/behavior-resolver.ts @@ -1,6 +1,6 @@ import { Item } from "@app/item"; import { IUpdateBehavior } from "./update-behavior.interface"; -import { LegacyBehavior } from "./implementations/legacy-behavior"; +import { DefaultBehavior } from "./implementations/default/default-behavior"; import { AgedBrieBehavior } from "./implementations/aged-brie/aged-brie-behavior"; import { BackstagePassBehavior } from "./implementations/backstage-pass/backstage-pass-behavior"; import { LegendaryItemBehavior } from "./implementations/legendary-item/legendary-item-behavior"; @@ -14,6 +14,6 @@ export function getUpdateBehaviorFor(item: Item): IUpdateBehavior { case "Sulfuras, Hand of Ragnaros": return new LegendaryItemBehavior(item); default: - return new LegacyBehavior(item); + return new DefaultBehavior(item); } } diff --git a/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts b/TypeScript/app/update-behaviors/implementations/default/default-behavior.spec.ts similarity index 68% rename from TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts rename to TypeScript/app/update-behaviors/implementations/default/default-behavior.spec.ts index 0458c851..92f60763 100644 --- a/TypeScript/app/update-behaviors/implementations/legacy-behavior.spec.ts +++ b/TypeScript/app/update-behaviors/implementations/default/default-behavior.spec.ts @@ -1,9 +1,9 @@ import { Item } from "@app/item"; -import { LegacyBehavior } from "./legacy-behavior"; +import { DefaultBehavior } from "./default-behavior"; -describe("Legacy Behavior", () => { +describe("Default Behavior", () => { it("should degrade sell inn and quality each day", () => { - const behavior = new LegacyBehavior(new Item("standard item", 1, 1)); + const behavior = new DefaultBehavior(new Item("standard item", 1, 1)); const result = behavior.update(); @@ -15,7 +15,7 @@ describe("Legacy Behavior", () => { }); it("should degrade quality twice as fast after sell in date", () => { - const behavior = new LegacyBehavior(new Item("standard item", 0, 2)); + const behavior = new DefaultBehavior(new Item("standard item", 0, 2)); const result = behavior.update(); @@ -27,7 +27,7 @@ describe("Legacy Behavior", () => { }); it("should not degrade quality below 0", () => { - const behavior = new LegacyBehavior(new Item("standard item", 1, 0)); + const behavior = new DefaultBehavior(new Item("standard item", 1, 0)); const result = behavior.update(); diff --git a/TypeScript/app/update-behaviors/implementations/default/default-behavior.ts b/TypeScript/app/update-behaviors/implementations/default/default-behavior.ts new file mode 100644 index 00000000..70e07a3f --- /dev/null +++ b/TypeScript/app/update-behaviors/implementations/default/default-behavior.ts @@ -0,0 +1,15 @@ +import { Item } from "@app/item"; +import { IUpdateBehavior } from "../../update-behavior.interface"; + +export class DefaultBehavior implements IUpdateBehavior { + constructor(private item: Item) {} + + update(): Item { + const amountToSubtract = this.item.sellIn <= 0 ? 2 : 1; + this.item.quality = Math.max(this.item.quality - amountToSubtract, 0); + + this.item.sellIn -= 1; + + return this.item; + } +} diff --git a/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts b/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts deleted file mode 100644 index 9bf81114..00000000 --- a/TypeScript/app/update-behaviors/implementations/legacy-behavior.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Item } from "@app/item"; -import { IUpdateBehavior } from "../update-behavior.interface"; - -export class LegacyBehavior implements IUpdateBehavior { - constructor(private item: Item) {} - - update(): Item { - if (this.item.quality > 0) { - this.item.quality = this.item.quality - 1; - } - - this.item.sellIn = this.item.sellIn - 1; - if (this.item.sellIn < 0) { - if (this.item.quality > 0) { - this.item.quality = this.item.quality - 1; - } - } - return this.item; - } -} From 15d47f8740b4d61e60fc289123a4abbc5e1d1052 Mon Sep 17 00:00:00 2001 From: Arno Chauveau Date: Thu, 24 Jul 2025 16:08:14 +0200 Subject: [PATCH 18/18] Added new functionality to support conjured items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add new ConjuredItemsBehavior - add conjured items behavior detection to resolver - update snapshots because they didn’t account for conjured items --- TypeScript/app/gilded-rose.spec.ts | 2 - .../behavior-resolver.spec.ts | 6 ++ .../app/update-behaviors/behavior-resolver.ts | 3 + .../conjured-item-behavior.spec.ts | 59 +++++++++++++++++++ .../conjured-item/conjured-item-behavior.ts | 16 +++++ .../__snapshots__/approvals.spec.ts.snap | 8 +-- 6 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 TypeScript/app/update-behaviors/implementations/conjured-item/conjured-item-behavior.spec.ts create mode 100644 TypeScript/app/update-behaviors/implementations/conjured-item/conjured-item-behavior.ts diff --git a/TypeScript/app/gilded-rose.spec.ts b/TypeScript/app/gilded-rose.spec.ts index e88057e0..2bf7175e 100644 --- a/TypeScript/app/gilded-rose.spec.ts +++ b/TypeScript/app/gilded-rose.spec.ts @@ -35,6 +35,4 @@ describe("Gilded Rose", () => { expect(updateMock).toHaveBeenCalledWith(item1); expect(updateMock).toHaveBeenCalledWith(item2); }); - - // to implement: "Conjured" items degrade in Quality twice as fast as normal items }); diff --git a/TypeScript/app/update-behaviors/behavior-resolver.spec.ts b/TypeScript/app/update-behaviors/behavior-resolver.spec.ts index 03af7f34..fe026f64 100644 --- a/TypeScript/app/update-behaviors/behavior-resolver.spec.ts +++ b/TypeScript/app/update-behaviors/behavior-resolver.spec.ts @@ -4,6 +4,7 @@ import { AgedBrieBehavior } from "./implementations/aged-brie/aged-brie-behavior import { DefaultBehavior } from "./implementations/default/default-behavior"; import { BackstagePassBehavior } from "./implementations/backstage-pass/backstage-pass-behavior"; import { LegendaryItemBehavior } from "./implementations/legendary-item/legendary-item-behavior"; +import { ConjuredItemBehavior } from "./implementations/conjured-item/conjured-item-behavior"; describe("Behavior resolver", () => { it("should correctly resolve Aged Brie", () => { @@ -25,6 +26,11 @@ describe("Behavior resolver", () => { getUpdateBehaviorFor(new Item("Sulfuras, Hand of Ragnaros", 0, 0)) ).toBeInstanceOf(LegendaryItemBehavior); }); + it("should correctly resolve Conjured Items", () => { + expect( + getUpdateBehaviorFor(new Item("Conjured Mana Cake", 0, 0)) + ).toBeInstanceOf(ConjuredItemBehavior); + }); it("should correctly resolve the rest to Legacy behavior", () => { expect( diff --git a/TypeScript/app/update-behaviors/behavior-resolver.ts b/TypeScript/app/update-behaviors/behavior-resolver.ts index ff856b10..c638e0ad 100644 --- a/TypeScript/app/update-behaviors/behavior-resolver.ts +++ b/TypeScript/app/update-behaviors/behavior-resolver.ts @@ -4,6 +4,7 @@ import { DefaultBehavior } from "./implementations/default/default-behavior"; import { AgedBrieBehavior } from "./implementations/aged-brie/aged-brie-behavior"; import { BackstagePassBehavior } from "./implementations/backstage-pass/backstage-pass-behavior"; import { LegendaryItemBehavior } from "./implementations/legendary-item/legendary-item-behavior"; +import { ConjuredItemBehavior } from "./implementations/conjured-item/conjured-item-behavior"; export function getUpdateBehaviorFor(item: Item): IUpdateBehavior { switch (item.name) { @@ -13,6 +14,8 @@ export function getUpdateBehaviorFor(item: Item): IUpdateBehavior { return new BackstagePassBehavior(item); case "Sulfuras, Hand of Ragnaros": return new LegendaryItemBehavior(item); + case "Conjured Mana Cake": + return new ConjuredItemBehavior(item); default: return new DefaultBehavior(item); } diff --git a/TypeScript/app/update-behaviors/implementations/conjured-item/conjured-item-behavior.spec.ts b/TypeScript/app/update-behaviors/implementations/conjured-item/conjured-item-behavior.spec.ts new file mode 100644 index 00000000..7f237f7b --- /dev/null +++ b/TypeScript/app/update-behaviors/implementations/conjured-item/conjured-item-behavior.spec.ts @@ -0,0 +1,59 @@ +import { Item } from "@app/item"; +import { ConjuredItemBehavior } from "./conjured-item-behavior"; + +describe("Conjured Item Behavior", () => { + it("should degrade quality with 2 and sellIn with 1 when sellIn is over 0", () => { + const behavior = new ConjuredItemBehavior( + new Item("Conjured Mana Cake", 1, 4) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Conjured Mana Cake", + sellIn: 0, + quality: 2, + }); + }); + it("should degrade quality with 4 and sellin with 1 when sellIn is equal to zero", () => { + const behavior = new ConjuredItemBehavior( + new Item("Conjured Mana Cake", 0, 5) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Conjured Mana Cake", + sellIn: -1, + quality: 1, + }); + }); + + it("should degrade quality with 4 and sellin with 1 when sellIn is under zero", () => { + const behavior = new ConjuredItemBehavior( + new Item("Conjured Mana Cake", -1, 5) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Conjured Mana Cake", + sellIn: -2, + quality: 1, + }); + }); + + it("shouldn't degrade quality under 0", () => { + const behavior = new ConjuredItemBehavior( + new Item("Conjured Mana Cake", 1, 1) + ); + + const result = behavior.update(); + + expect(result).toMatchObject({ + name: "Conjured Mana Cake", + sellIn: 0, + quality: 0, + }); + }); +}); diff --git a/TypeScript/app/update-behaviors/implementations/conjured-item/conjured-item-behavior.ts b/TypeScript/app/update-behaviors/implementations/conjured-item/conjured-item-behavior.ts new file mode 100644 index 00000000..007eb6e1 --- /dev/null +++ b/TypeScript/app/update-behaviors/implementations/conjured-item/conjured-item-behavior.ts @@ -0,0 +1,16 @@ +import { Item } from "@app/item"; +import { IUpdateBehavior } from "@app/update-behaviors/update-behavior.interface"; + +export class ConjuredItemBehavior implements IUpdateBehavior { + constructor(private item: Item) {} + + update(): Item { + const amountToSubtract = this.item.sellIn <= 0 ? 4 : 2; + + this.item.quality = Math.max(this.item.quality - amountToSubtract, 0); + + this.item.sellIn -= 1; + + return this.item; + } +} diff --git a/TypeScript/snapshot-tests/__snapshots__/approvals.spec.ts.snap b/TypeScript/snapshot-tests/__snapshots__/approvals.spec.ts.snap index 6b0fc024..3f299ad2 100644 --- a/TypeScript/snapshot-tests/__snapshots__/approvals.spec.ts.snap +++ b/TypeScript/snapshot-tests/__snapshots__/approvals.spec.ts.snap @@ -44,7 +44,7 @@ exports[`Gilded Rose Approval should match the snapshot for thirty Days 1`] = ` }, Item { "name": "Conjured Mana Cake", - "quality": 5, + "quality": 4, "sellIn": 2, }, ] @@ -94,7 +94,7 @@ exports[`Gilded Rose Approval should match the snapshot for thirty Days 2`] = ` }, Item { "name": "Conjured Mana Cake", - "quality": 4, + "quality": 2, "sellIn": 1, }, ] @@ -144,7 +144,7 @@ exports[`Gilded Rose Approval should match the snapshot for thirty Days 3`] = ` }, Item { "name": "Conjured Mana Cake", - "quality": 3, + "quality": 0, "sellIn": 0, }, ] @@ -194,7 +194,7 @@ exports[`Gilded Rose Approval should match the snapshot for thirty Days 4`] = ` }, Item { "name": "Conjured Mana Cake", - "quality": 1, + "quality": 0, "sellIn": -1, }, ]