Created
April 2, 2020 10:50
-
-
Save FongX777/fb36c10fb698e69adbf027044cfc88ec to your computer and use it in GitHub Desktop.
GildedRose-Refactoring-Kata JS Results
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { Shop, Item, SpecialItemName } = require('../src/gilded_rose'); | |
const { ITEM_AGED_BRIE, ITEM_SULFURAS, ITEM_BACKSTAGE_PASS } = SpecialItemName; | |
describe('Gilded Rose', function() { | |
it('the item foo should degrade both its quality and sellIn day', function() { | |
const gildedRose = new Shop([new Item('foo', 10, 10)]); | |
const items = gildedRose.updateQuality(); | |
const testItem = items[0]; | |
expect(testItem.name).toBe('foo'); | |
expect(testItem.sellIn).toBe(9); | |
expect(testItem.quality).toBe(9); | |
}); | |
it('all items should degrade both its quality and sellIn day', function() { | |
const itemsToPassIn = [new Item('foo', 10, 10), new Item('bar', 10, 10)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
const items = gildedRose.updateQuality(); | |
const testItem1 = items[0]; | |
expect(testItem1.name).toBe('foo'); | |
expect(testItem1.sellIn).toBe(9); | |
expect(testItem1.quality).toBe(9); | |
const testItem2 = items[1]; | |
expect(testItem2.name).toBe('bar'); | |
expect(testItem2.sellIn).toBe(9); | |
expect(testItem2.quality).toBe(9); | |
}); | |
// 商品的品質的上限為50。 | |
it('should 商品的品質的上限為 50', function() { | |
expect(() => { | |
const itemsToPassIn = [new Item('foo', 10, 60)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
return gildedRose; | |
}).toThrow('Quality Must < 50'); | |
}); | |
it('should 商品的品質的上限為 50 (2)', function() { | |
const itemsToPassIn = [new Item(ITEM_AGED_BRIE, 10, 50)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
const items = gildedRose.updateQuality(); | |
const testItem1 = items[0]; | |
expect(testItem1.sellIn).toBe(9); | |
expect(testItem1.quality).toBe(50); | |
}); | |
// 商品的品質不能為負數。 | |
it('商品的品質不能為負數', function() { | |
const itemsToPassIn = [new Item('foo', 10, 0)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
const items = gildedRose.updateQuality(); | |
const testItem1 = items[0]; | |
expect(testItem1.name).toBe('foo'); | |
expect(testItem1.sellIn).toBe(9); | |
expect(testItem1.quality).toBe(0); | |
gildedRose.updateQuality(); | |
expect(testItem1.name).toBe('foo'); | |
expect(testItem1.sellIn).toBe(8); | |
expect(testItem1.quality).toBe(0); | |
}); | |
// 一但商品過了銷售剩餘天數之後還沒未賣出,那麼其每日品質下降的速度就會加倍。 | |
it('一但商品過了銷售剩餘天數之後還沒未賣出,那麼其每日品質下降的速度就會加倍', function() { | |
const itemsToPassIn = [new Item('foo', 0, 10)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
const items = gildedRose.updateQuality(); | |
const testItem1 = items[0]; | |
expect(testItem1.name).toBe('foo'); | |
expect(testItem1.sellIn).toBe(-1); | |
expect(testItem1.quality).toBe(8); | |
}); | |
// 陳年乾酪(Aged Brie)的品質值隨著時間的推移,不減反增。 | |
it('陳年乾酪(Aged Brie)的品質值隨著時間的推移,不減反增', function() { | |
const itemsToPassIn = [new Item(ITEM_AGED_BRIE, 10, 10)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
const items = gildedRose.updateQuality(); | |
const testItem1 = items[0]; | |
expect(testItem1.name).toBe(ITEM_AGED_BRIE); | |
expect(testItem1.sellIn).toBe(9); | |
expect(testItem1.quality).toBe(11); | |
}); | |
// 魔法錘(Sulfuras)是一個傳奇商品,其銷售剩餘天數和品質都不會變化。 | |
it('魔法錘(Sulfuras)是一個傳奇商品,其銷售剩餘天數和品質都不會變化。', function() { | |
const itemsToPassIn = [new Item(ITEM_SULFURAS, 10, 10)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
const items = gildedRose.updateQuality(); | |
const testItem1 = items[0]; | |
expect(testItem1.name).toBe(ITEM_SULFURAS); | |
expect(testItem1.sellIn).toBe(10); | |
expect(testItem1.quality).toBe(10); | |
}); | |
describe('劇院後台通行證(Backstage passes)', function() { | |
it('就像陳年乾酪一樣,其品質會隨著銷售剩餘天數的減少而提高', function() { | |
const itemsToPassIn = [new Item(ITEM_BACKSTAGE_PASS, 20, 10)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
const items = gildedRose.updateQuality(); | |
const testItem1 = items[0]; | |
expect(testItem1.name).toBe(ITEM_BACKSTAGE_PASS); | |
expect(testItem1.sellIn).toBe(19); | |
expect(testItem1.quality).toBe(11); | |
}); | |
it('當離演出開始不到10天時,品質每日提高2', function() { | |
const itemsToPassIn = [new Item(ITEM_BACKSTAGE_PASS, 10, 10)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
const items = gildedRose.updateQuality(); | |
const testItem1 = items[0]; | |
expect(testItem1.name).toBe(ITEM_BACKSTAGE_PASS); | |
expect(testItem1.sellIn).toBe(9); | |
expect(testItem1.quality).toBe(12); | |
}); | |
it('當不到5天時,品質值每日提高3', function() { | |
const itemsToPassIn = [new Item(ITEM_BACKSTAGE_PASS, 5, 10)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
const items = gildedRose.updateQuality(); | |
const testItem1 = items[0]; | |
expect(testItem1.name).toBe(ITEM_BACKSTAGE_PASS); | |
expect(testItem1.sellIn).toBe(4); | |
expect(testItem1.quality).toBe(13); | |
}); | |
it('當演出結束後,品質歸0', function() { | |
const itemsToPassIn = [new Item(ITEM_BACKSTAGE_PASS, 0, 10)]; | |
const gildedRose = new Shop(itemsToPassIn); | |
const items = gildedRose.updateQuality(); | |
const testItem1 = items[0]; | |
expect(testItem1.name).toBe(ITEM_BACKSTAGE_PASS); | |
expect(testItem1.sellIn).toBe(-1); | |
expect(testItem1.quality).toBe(0); | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const ITEM_AGED_BRIE = 'Aged Brie'; | |
const ITEM_BACKSTAGE_PASS = 'Backstage passes to a TAFKAL80ETC concert'; | |
const ITEM_SULFURAS = 'Sulfuras, Hand of Ragnaros'; | |
class Item { | |
constructor(name, sellIn, quality) { | |
this.name = name; | |
this.sellIn = sellIn; | |
this.quality = quality; | |
if (quality > 50) { | |
throw new Error('Quality Must < 50'); | |
} | |
} | |
isUnderMaxQuality() { | |
return this.quality < 50; | |
} | |
passOneDay() { | |
const item = this; | |
if (NotAffectedItemPolicy.canApplyTo(item)) { | |
NotAffectedItemPolicy.passOneDay(item); | |
return; | |
} | |
if (QualityIncreaseByTimeItemPolicy.canApplyTo(item)) { | |
QualityIncreaseByTimeItemPolicy.passOneDay(item); | |
} | |
if (TicketItemPolicy.canApplyTo(item)) { | |
TicketItemPolicy.passOneDay(item); | |
return; | |
} | |
if (NormalItemPolicy.canApplyTo(item)) { | |
NormalItemPolicy.passOneDay(item); | |
return; | |
} | |
} | |
} | |
class NotAffectedItemPolicy { | |
static canApplyTo(item) { | |
return item.name === ITEM_SULFURAS; | |
} | |
static passOneDay(item) { | |
return item; | |
} | |
} | |
class NormalItemPolicy { | |
static canApplyTo(item) { | |
return ![ITEM_AGED_BRIE, ITEM_BACKSTAGE_PASS, ITEM_SULFURAS].includes( | |
item.name | |
); | |
} | |
static passOneDay(item) { | |
if (!NormalItemPolicy.canApplyTo(item)) { | |
throw new Error('Not Match'); | |
} | |
item.sellIn = item.sellIn - 1; | |
if (item.sellIn < 0) { | |
item.quality = item.quality - 2; | |
return; | |
} | |
if (item.quality > 0) { | |
item.quality = item.quality - 1; | |
} | |
} | |
} | |
class QualityIncreaseByTimeItemPolicy { | |
static canApplyTo(item) { | |
return item.name === ITEM_AGED_BRIE; | |
} | |
static passOneDay(item) { | |
if (!QualityIncreaseByTimeItemPolicy.canApplyTo(item)) { | |
throw new Error('Wrong Name'); | |
} | |
item.sellIn = item.sellIn - 1; | |
if (item.isUnderMaxQuality()) { | |
item.quality = item.quality + 1; | |
} | |
return; | |
} | |
} | |
class TicketItemPolicy { | |
static canApplyTo(item) { | |
return item.name === ITEM_BACKSTAGE_PASS; | |
} | |
static passOneDay(item) { | |
item.sellIn = item.sellIn - 1; | |
if (item.sellIn < 0) { | |
item.quality = 0; | |
return; | |
} | |
if (!item.isUnderMaxQuality()) { | |
return; | |
} | |
item.quality = item.quality + 1; | |
if (item.sellIn < 11) { | |
item.quality = item.quality + 1; | |
} | |
if (item.sellIn < 6) { | |
item.quality = item.quality + 1; | |
} | |
return; | |
} | |
} | |
class Shop { | |
/** | |
* @param {Item[]} items | |
*/ | |
constructor(items = []) { | |
this.items = items; | |
} | |
updateQuality() { | |
this.items.forEach(item => item.passOneDay()); | |
return this.items; | |
} | |
} | |
module.exports = { | |
Item, | |
Shop, | |
SpecialItemName: { | |
ITEM_AGED_BRIE, | |
ITEM_BACKSTAGE_PASS, | |
ITEM_SULFURAS | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi Fong 感謝提供練習範例,讓我對重構後的模式又有了新的認知 XDD
想補充的一點是 TicketItemPolicy 裡的 passOneDay 好像跟原本的邏輯有點不一樣
原本邏輯是扣除銷售日期前是 sell In 是小於 11 及 小於 6 時才會去增加 quality
但因為重構後是先扣除銷售日期,所以 sell In 判斷應該減為 10 跟 5 才對
這可能是另一個議題是重構前單元測試要寫到多仔細,邊界測試可能要寫不夠滿嗎 QQ