diff --git a/app/gilded-rose.ts b/app/gilded-rose.ts index ee55134..274081a 100644 --- a/app/gilded-rose.ts +++ b/app/gilded-rose.ts @@ -1,69 +1,109 @@ -export class Item { - name: string; - sellIn: number; - quality: number; - - constructor(name, sellIn, quality) { - this.name = name; - this.sellIn = sellIn; - this.quality = quality; - } -} - -export class GildedRose { - items: Array; - - constructor(items = [] as Array) { - this.items = items; - } - - 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].quality > 0) { - 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') { - if (this.items[i].sellIn < 11) { - if (this.items[i].quality < 50) { - 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 - } - } - } - } - } - 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].quality > 0) { - 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 - } - } else { - if (this.items[i].quality < 50) { - this.items[i].quality = this.items[i].quality + 1 - } - } - } - } - - return this.items; - } -} \ No newline at end of file +export class Item { + name: string; + sellIn: number; + quality: number; + + static readonly MIN_SELL_IN = 0; + static readonly MIN_QUALITY = 0; + static readonly MAX_QUALITY = 50; + + constructor(name: string, sellIn: number, quality: number) { + this.name = name; + this.sellIn = sellIn; + this.quality = quality; + } +} + +export class GildedRose { + items: Item[]; + + constructor(items: Item[] = []) { + this.items = items; + } + + /** + * Update the quality of all items. + * + * "Backstage passes" – increase in quality as the sellIn date approaches. + * "Conjured" – degrade in quality twice as fast. + * "Aged Brie" – increases in quality as it gets older. + * "Sulfuras" – not updated. + * + * @returns {Item[]} - The updated list of items. + */ + updateQuality(): Item[] { + this.items.forEach(item => { + this.updateItem(item); + item.sellIn = Math.max(Item.MIN_SELL_IN, item.sellIn); + this.clampQuality(item); + }); + return this.items; + } + + /** + * Update a single item's quality and sellIn value. + * Sulfuras items are not updated. + * + * @param {Item} item - The item to update. + */ + private updateItem(item: Item): void { + const name = item.name.toLowerCase(); + + if (name.includes('sulfuras')) { + this.clampQuality(item); + item.sellIn = Math.max(Item.MIN_SELL_IN, item.sellIn); + return; + } + + if (name.includes('aged brie')) { + this.updateAgedBrie(item); + } else if (name.includes('backstage passes')) { + this.updateBackstagePasses(item); + } else if (name.includes('conjured')) { + this.updateConjured(item); + } else { + this.updateNormal(item); + } + + item.sellIn--; + } + + private clampQuality(item: Item): void { + item.quality = Math.max(Item.MIN_QUALITY, Math.min(Item.MAX_QUALITY, item.quality)); + } + + private updateNormal(item: Item): void { + const factor = item.sellIn <= Item.MIN_SELL_IN ? 2 : 1; + this.decrease(item, factor); + } + + private updateConjured(item: Item): void { + const factor = item.sellIn <= Item.MIN_SELL_IN ? 4 : 2; + this.decrease(item, factor); + } + + private updateAgedBrie(item: Item): void { + const factor = item.sellIn <= Item.MIN_SELL_IN ? 2 : 1; + this.increase(item, factor); + } + + private updateBackstagePasses(item: Item): void { + if (item.sellIn < 0) { + item.quality = Item.MIN_QUALITY; + } else if (item.sellIn <= 5) { + this.increase(item, 3); + } else if (item.sellIn <= 10) { + this.increase(item, 2); + } else { + this.increase(item, 1); + } + } + + private increase(item: Item, amount: number): void { + item.quality = Math.min(Item.MAX_QUALITY, item.quality + amount); + } + + private decrease(item: Item, amount: number): void { + item.quality = Math.max(Item.MIN_QUALITY, item.quality - amount); + } +} diff --git a/test/golden-master-text-test.ts b/test/golden-master-text-test.ts index ff02ea6..df33ec7 100644 --- a/test/golden-master-text-test.ts +++ b/test/golden-master-text-test.ts @@ -1,19 +1,24 @@ 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)]; - + // Other items - Normal + new Item("+5 Dexterity Vest", 10, 20), // 0 + new Item("Aged Brie", 2, 0), // 1 + new Item("Elixir of the Mongoose", 5, 7), // 2 + new Item("Griphon Wings", 5, 42), // 3 + new Item("Normal Item", 3, 6), // 4 + // Aged Brie + new Item("Aged Brie", 2, 0), // 5 + new Item("Aged Brie", 0, 0), // 6 + // Backstage passes + new Item("Backstage passes to a TAFKAL80ETC concert", 15, 20), // 9 + new Item("Backstage passes to a TAFKAL81ETC concert", 10, 49), // 10 + new Item("Backstage passes to a TAFKAL82ETC concert", 5, 49), // 11 + new Item("Backstage passes to a TAFKAL83ETC concert", 0, 5), // 12 + // Conjured + new Item("Conjured Mana Cake", 3, 6), // 13 + new Item("Conjured Mana Cake", 4, 7) // 14 +]; const gildedRose = new GildedRose(items); @@ -26,8 +31,10 @@ for (let i = 0; i < days + 1; i++) { console.log("-------- day ".padStart(25) + i + " --------"); console.log("name".padStart(24).padEnd(45) + "| sellIn |" + "quality"); items.forEach(element => { - console.log(element.name.padEnd(45) + '|' + element.sellIn.toString().padStart(5).padEnd(8) + '|' + element.quality.toString().padStart(4)); - + console.log( + element.name.padEnd(45) + '|' + + element.sellIn.toString().padStart(5).padEnd(8) + '|' + + element.quality.toString().padStart(4)); }); console.log(); gildedRose.updateQuality(); diff --git a/test/jest/gilded-rose.spec.ts b/test/jest/gilded-rose.spec.ts index 613639f..0c047a4 100644 --- a/test/jest/gilded-rose.spec.ts +++ b/test/jest/gilded-rose.spec.ts @@ -1,25 +1,192 @@ import { Item, GildedRose } from '@/gilded-rose'; -describe('Gilded Rose', () => { - it('should foo', () => { - // Arrange - const gildedRose = new GildedRose([new Item('foo', 0, 0)]); +describe('General Items', () => { + it('name remains unchanged', () => { + const gr = new GildedRose([new Item('foo', 5, 10)]); + const items = gr.updateQuality(); + expect(items[0].name).toBe('foo'); + }); - // Act - const items = gildedRose.updateQuality(); + it('Sword: quality decreases by 1 and sellIn decreases', () => { + const gr = new GildedRose([new Item('Sword', 1, 2)]); + const items = gr.updateQuality(); + expect(items[0].quality).toBe(1); + expect(items[0].sellIn).toBe(0); + }); - // Assert - expect(items[0].name).toBe('bar'); - }); + it('Vest: normal item update (sellIn > 0)', () => { + const gr = new GildedRose([new Item("+5 Dexterity Vest", 10, 20)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(9); + expect(items[0].quality).toBe(19); + }); - it('sword quality drops by 1', () => { - // Arrange - const gildedRose = new GildedRose([new Item('Sword', 1, 1)]); + it('Elixir: normal item update (sellIn > 0)', () => { + const gr = new GildedRose([new Item("Elixir of the Mongoose", 5, 7)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(4); + expect(items[0].quality).toBe(6); + }); - // Act - const items = gildedRose.updateQuality(); + it('Griphon Wings: normal item update (sellIn > 0)', () => { + const gr = new GildedRose([new Item("Griphon Wings", 5, 42)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(4); + expect(items[0].quality).toBe(41); + }); - // Assert - expect(items[0].quality).toBe(1); - }) + it('Normal Item: update when sellIn > 0', () => { + const gr = new GildedRose([new Item("Normal Item", 3, 6)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(2); + expect(items[0].quality).toBe(5); + }); + + it('Normal Item: update when sellIn is 0 (quality decreases by 2)', () => { + const gr = new GildedRose([new Item("Normal Item", 0, 6)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(0); + expect(items[0].quality).toBe(4); + }); + + it('should clamp quality to MAX if above maximum', () => { + const gr = new GildedRose([new Item("Normal Item", 5, 60)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(4); + expect(items[0].quality).toBe(50); + }); +}); + +describe('Aged Brie', () => { + it('basic update', () => { + const gr = new GildedRose([new Item("Aged Brie", 2, 0)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(1); + expect(items[0].quality).toBe(1); + }); + + it('sellIn positive: quality increases by 1', () => { + const gr = new GildedRose([new Item("Aged Brie", 5, 10)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(4); + expect(items[0].quality).toBe(11); + }); + + it('sellIn 0: quality increases by 2', () => { + const gr = new GildedRose([new Item("Aged Brie", 0, 10)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(0); + expect(items[0].quality).toBe(12); + }); + + it('quality is capped at MAX', () => { + const gr = new GildedRose([new Item("Aged Brie", 1, 49)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(0); + expect(items[0].quality).toBe(50); + }); +}); + +describe('Sulfuras', () => { + it('negative sellIn: clamped to 0 and quality clamped to MAX', () => { + const gr = new GildedRose([new Item("Sulfuras, Hand of Ragnaros", -1, 80)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(0); + expect(items[0].quality).toBe(50); + }); + + it('sellIn 0: remains unchanged', () => { + const gr = new GildedRose([new Item("Sulfuras, Hand of Ragnaros", 0, 80)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(0); + expect(items[0].quality).toBe(50); + }); + + it('positive sellIn: remains unchanged', () => { + const gr = new GildedRose([new Item("Sulfuras, Hand of Ragnaros", 5, 60)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(5); + expect(items[0].quality).toBe(50); + }); +}); + +describe('Backstage Passes', () => { + it('sellIn > 10: quality increases by 1', () => { + const gr = new GildedRose([new Item("Backstage passes to a TAFKAL80ETC concert", 15, 20)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(14); + expect(items[0].quality).toBe(21); + }); + + it('sellIn exactly 10: quality increases by 2', () => { + const gr = new GildedRose([new Item("Backstage passes to a TAFKAL80ETC concert", 10, 20)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(9); + expect(items[0].quality).toBe(22); + }); + + it('sellIn <= 5: quality increases by 3', () => { + const gr = new GildedRose([new Item("Backstage passes to a TAFKAL82ETC concert", 5, 49)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(4); + expect(items[0].quality).toBe(50); + }); + + it('sellIn 0: sellIn remains 0 and quality increases by 3', () => { + const gr = new GildedRose([new Item("Backstage passes to a TAFKAL83ETC concert", 0, 5)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(0); + expect(items[0].quality).toBe(8); + }); + + it('sellIn < 0: sellIn and quality are clamped to 0', () => { + const gr = new GildedRose([new Item("Backstage passes to a TAFKAL84ETC concert", -1, 9)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(0); + expect(items[0].quality).toBe(0); + }); + + it('other case: update pass normally', () => { + const gr = new GildedRose([new Item("Backstage passes to a TAFKAL85ETC concert", 4, 0)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(3); + expect(items[0].quality).toBe(3); + }); + + it('max cap: quality does not exceed MAX', () => { + const gr = new GildedRose([new Item("Backstage passes to a TAFKAL86ETC concert", 8, 50)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(7); + expect(items[0].quality).toBe(50); + }); +}); + +describe('Conjured Items', () => { + it('case 1: decrease quality by 4 when sellIn <= 0', () => { + const gr = new GildedRose([new Item("Conjured Mana Cake", 0, 6)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(0); + expect(items[0].quality).toBe(2); + }); + + it('case 2: decrease quality by 2 when sellIn > 0', () => { + const gr = new GildedRose([new Item("Conjured Mana Cake", 4, 7)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(3); + expect(items[0].quality).toBe(5); + }); + + it('should not go negative quality', () => { + const gr = new GildedRose([new Item("Conjured Mana Cake", 0, -1)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(0); + expect(items[0].quality).toBe(0); + }); + + it('should cap quality at MIN for conjured items', () => { + const gr = new GildedRose([new Item("Conjured Mana Cake", 0, 1)]); + const items = gr.updateQuality(); + expect(items[0].sellIn).toBe(0); + expect(items[0].quality).toBe(0); + }); });