|
// File: gilded-rose.ts |
|
// For: https://gist.github.com/peerreynders/146d5a2c7d6adf5358580da5054c76b4 |
|
|
|
type Item = { |
|
name: string; |
|
sellIn: number; |
|
quality: number; |
|
}; |
|
|
|
function makeItem(name: string, sellIn: number, quality: number): Item { |
|
return { |
|
name, |
|
sellIn, |
|
quality, |
|
}; |
|
} |
|
|
|
type UpdateQuality = (item: Item) => void; |
|
|
|
/* |
|
* BEFORE implementation |
|
* |
|
* Original: |
|
* https://github.com/emilybache/GildedRose-Refactoring-Kata/blob/main/TypeScript/app/gilded-rose.ts |
|
* |
|
*/ |
|
function updateQualityBefore(item: 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; |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* AFTER implementation |
|
* |
|
* Requirements: |
|
* https://github.com/emilybache/GildedRose-Refactoring-Kata/blob/main/GildedRoseRequirements.txt |
|
* |
|
*/ |
|
type QualityAdjustmentFn = (item: Item) => number; |
|
|
|
// - At the end of each day our system lowers both values for every item |
|
// - Once the sell by date has passed, Quality degrades twice as fast |
|
const qualityAdjustment: QualityAdjustmentFn = ({ sellIn }) => |
|
sellIn > 0 ? -1 : -2; |
|
|
|
// - "Aged Brie" actually increases in Quality the older it gets |
|
const brieAdjustment: QualityAdjustmentFn = (item) => -qualityAdjustment(item); |
|
|
|
// - "Backstage passes", like aged brie, |
|
// increases in Quality as its SellIn value approaches; |
|
// Quality increases by 2 when there are 10 days or less and |
|
// by 3 when there are 5 days or less but Quality |
|
// drops to 0 after the concert |
|
const passesAdjustment: QualityAdjustmentFn = (item) => |
|
item.sellIn > 10 |
|
? -qualityAdjustment(item) |
|
: item.sellIn > 5 |
|
? -2 * qualityAdjustment(item) |
|
: item.sellIn > 0 |
|
? -3 * qualityAdjustment(item) |
|
: -item.quality; |
|
|
|
// - "Conjured" items degrade in Quality twice as fast as normal items |
|
const conjuredAdjustment: QualityAdjustmentFn = (item) => |
|
2 * qualityAdjustment(item); |
|
|
|
const clamp = (min: number, max: number, value: number) => |
|
value > max ? max : value < min ? min : value; |
|
|
|
// - At the end of each day our system lowers both values for every item |
|
// - The Quality of an item is never negative |
|
// - The Quality of an item is never more than 50 |
|
function updateItem(item: Item, fn: QualityAdjustmentFn) { |
|
const quality = clamp(0, 50, item.quality + fn(item)); |
|
|
|
item.sellIn -= 1; |
|
item.quality = quality; |
|
} |
|
|
|
type ConfigEntry = [name: string, update: UpdateQuality]; |
|
|
|
function selectUpdate(itemName: string, config: ConfigEntry[]) { |
|
for (let i = 1; i < config.length; i += 1) { |
|
const [name, update] = config[i]; |
|
if (itemName.startsWith(name)) return update; |
|
} |
|
return config[0][1]; |
|
} |
|
|
|
const NAME_BRIE = 'Aged Brie'; |
|
const NAME_SULFARAS = 'Sulfuras'; |
|
const NAME_PASSES = 'Backstage passes'; |
|
const NAME_CONJURED = 'Conjured'; |
|
|
|
const config2: ConfigEntry[] = [ |
|
['', (item: Item) => updateItem(item, qualityAdjustment)], |
|
[NAME_BRIE, (item: Item) => updateItem(item, brieAdjustment)], |
|
[NAME_SULFARAS, (item: Item) => void 0], |
|
[NAME_PASSES, (item: Item) => updateItem(item, passesAdjustment)], |
|
[NAME_CONJURED, (item: Item) => updateItem(item, conjuredAdjustment)], |
|
]; |
|
|
|
const updateQualityAfter = (item: Item) => |
|
selectUpdate(item.name, config2)(item); |
|
|
|
/* |
|
* TESTS |
|
*/ |
|
function tests(updateQuality: UpdateQuality) { |
|
// NOTE: |
|
// Assertion failures show in the |
|
// Developer Tools Console NOT the `Logs` panel |
|
const assert = console.assert; |
|
const isExpected = (item: Item, sellIn: number, quality: number) => |
|
item.sellIn === sellIn && item.quality === quality; |
|
|
|
const test01 = () => { |
|
const title = 'Both sellIn and quality are lowered'; |
|
const item = makeItem('Elixir of the Mongoose', 5, 7); |
|
updateQuality(item); |
|
assert( |
|
isExpected(item, 4, 6), |
|
`${title}: sellIn ${item.sellIn} and/or quality ${item.quality} don't match lower values` |
|
); |
|
}; |
|
test01(); |
|
|
|
const test02 = () => { |
|
const title = 'Quality degrades twice as fast once past sellIn'; |
|
const item = makeItem('Elixir of the Mongoose', 0, 7); |
|
updateQuality(item); |
|
assert( |
|
isExpected(item, -1, 5), |
|
`${title}: quality ${item.quality} doesn't match degraded value` |
|
); |
|
}; |
|
test02(); |
|
|
|
const test03 = () => { |
|
const title = 'Quality is never negative'; |
|
const item = makeItem('Elixir of the Mongoose', 5, 0); |
|
updateQuality(item); |
|
assert( |
|
isExpected(item, 4, 0), |
|
`${title}: quality ${item.quality} isn't zero` |
|
); |
|
}; |
|
test03(); |
|
|
|
const test04 = () => { |
|
const title = 'Aged Brie increases in quality'; |
|
const item = makeItem('Aged Brie', 2, 0); |
|
updateQuality(item); |
|
assert( |
|
isExpected(item, 1, 1), |
|
`${title}: quality ${item.quality} didn't increase` |
|
); |
|
}; |
|
test04(); |
|
|
|
const test05 = () => { |
|
const title = 'Quality never exceeds 50'; |
|
const item = makeItem('Aged Brie', 2, 50); |
|
updateQuality(item); |
|
assert( |
|
isExpected(item, 1, 50), |
|
`${title}: quality ${item.quality} exceeds maximum` |
|
); |
|
}; |
|
test05(); |
|
|
|
const test05A = () => { |
|
const title = 'Aged Brie quality increase doubles past sellIn'; |
|
const item = makeItem('Aged Brie', 0, 20); |
|
updateQuality(item); |
|
assert( |
|
isExpected(item, -1, 22), |
|
`${title}: quality ${item.quality} doesn't match double increase` |
|
); |
|
}; |
|
test05A(); |
|
|
|
const test06 = () => { |
|
const title = `Sulfaras doesn't change`; |
|
const item = makeItem('Sulfuras, Hand of Ragnaros', -1, 80); |
|
updateQuality(item); |
|
assert(isExpected(item, -1, 80), `${title}: item unexpectedly changed`); |
|
}; |
|
test06(); |
|
|
|
const test07 = () => { |
|
const title = 'Backstages passes increase in quality (> 10 days)'; |
|
const item = makeItem('Backstage passes to a TAFKAL80ETC concert', 11, 20); |
|
updateQuality(item); |
|
assert( |
|
isExpected(item, 10, 21), |
|
`${title}: quality ${item.quality} didn't increase` |
|
); |
|
}; |
|
test07(); |
|
|
|
const test08 = () => { |
|
const title = 'Backstages passes increase by 2 in quality (11 > days > 5)'; |
|
const item1 = makeItem('Backstage passes to a TAFKAL80ETC concert', 10, 20); |
|
const item2 = makeItem('Backstage passes to a TAFKAL80ETC concert', 6, 30); |
|
updateQuality(item1); |
|
updateQuality(item2); |
|
assert( |
|
isExpected(item1, 9, 22), |
|
`${title}: quality ${item1.quality} didn't increase by 2` |
|
); |
|
assert( |
|
isExpected(item2, 5, 32), |
|
`${title}: quality ${item2.quality} didn't increase by 2` |
|
); |
|
}; |
|
test08(); |
|
|
|
const test09 = () => { |
|
const title = 'Backstages passes increase by 3 in quality (6 > days > -1)'; |
|
const item1 = makeItem('Backstage passes to a TAFKAL80ETC concert', 5, 20); |
|
const item2 = makeItem('Backstage passes to a TAFKAL80ETC concert', 1, 30); |
|
updateQuality(item1); |
|
updateQuality(item2); |
|
assert( |
|
isExpected(item1, 4, 23), |
|
`${title}: quality ${item1.quality} didn't increase by 3` |
|
); |
|
assert( |
|
isExpected(item2, 0, 33), |
|
`${title}: quality ${item2.quality} didn't increase by 3` |
|
); |
|
}; |
|
test09(); |
|
|
|
const test10 = () => { |
|
const title = 'Backstages passes quality drops to zero'; |
|
const item = makeItem('Backstage passes to a TAFKAL80ETC concert', 0, 30); |
|
updateQuality(item); |
|
assert( |
|
isExpected(item, -1, 0), |
|
`${title}: quality ${item.quality} didn't drop to zero` |
|
); |
|
}; |
|
test10(); |
|
|
|
if (updateQuality === updateQualityAfter) { |
|
const test11 = () => { |
|
const title = 'Conjured quality degrades twice as fast BEFORE sellIn'; |
|
const item = makeItem('Conjured Mana Cake', 3, 6); |
|
updateQuality(item); |
|
assert( |
|
isExpected(item, 2, 4), |
|
`${title}: quality ${item.quality} doesn't match twice degraded value` |
|
); |
|
}; |
|
test11(); |
|
|
|
const test12 = () => { |
|
const title = 'Conjured quality degrades twice as fast AFTER sellIn'; |
|
const item = makeItem('Conjured Mana Cake', 0, 6); |
|
updateQuality(item); |
|
assert( |
|
isExpected(item, -1, 2), |
|
`${title}: quality ${item.quality} doesn't match twice degraded value` |
|
); |
|
}; |
|
test12(); |
|
} |
|
|
|
console.log('tests DONE'); |
|
} |
|
|
|
/* |
|
* DEMO |
|
* |
|
* Original: |
|
* https://github.com/emilybache/GildedRose-Refactoring-Kata/blob/main/TypeScript/test/golden-master-text-test.ts |
|
*/ |
|
function demo(updateQuality: UpdateQuality) { |
|
const data: [string, number, number][] = [ |
|
['+5 Dexterity Vest', 10, 20], |
|
['Aged Brie', 2, 0], |
|
['Elixir of the Mongoose', 5, 7], |
|
['Sulfuras, Hand of Ragnaros', 0, 80], |
|
['Sulfuras, Hand of Ragnaros', -1, 80], |
|
['Backstage passes to a TAFKAL80ETC concert', 15, 20], |
|
['Backstage passes to a TAFKAL80ETC concert', 10, 49], |
|
['Backstage passes to a TAFKAL80ETC concert', 5, 49], |
|
]; |
|
|
|
if (updateQuality === updateQualityAfter) { |
|
data.push(['Conjured Mana Cake', 3, 6]); |
|
} |
|
|
|
const items = data.map(([name, sellIn, quality]) => |
|
makeItem(name, sellIn, quality) |
|
); |
|
|
|
const log = console.log; |
|
const days = 2; |
|
const displayItem = ({ name, sellIn, quality }: Item) => |
|
log(`${name} ${sellIn} ${quality}`); |
|
|
|
for (let i = days; i > 0; i -= 1) { |
|
log(`-------- day ${days - i} --------`); |
|
log(`name, sellIn, quality`); |
|
items.forEach(displayItem); |
|
log(); |
|
items.forEach(updateQuality); |
|
} |
|
} |
|
|
|
/* |
|
* RUN Script |
|
*/ |
|
// tests(updateQualityBefore); |
|
tests(updateQualityAfter); |
|
|
|
// demo(updateQualityBefore); |
|
// demo(updateQualityAfter); |