Skip to content

Instantly share code, notes, and snippets.

@FongX777
Created April 2, 2020 10:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FongX777/fb36c10fb698e69adbf027044cfc88ec to your computer and use it in GitHub Desktop.
Save FongX777/fb36c10fb698e69adbf027044cfc88ec to your computer and use it in GitHub Desktop.
GildedRose-Refactoring-Kata JS Results
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);
});
});
});
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
}
};
@Nick0603
Copy link

Hi Fong 感謝提供練習範例,讓我對重構後的模式又有了新的認知 XDD
想補充的一點是 TicketItemPolicy 裡的 passOneDay 好像跟原本的邏輯有點不一樣
原本邏輯是扣除銷售日期前是 sell In 是小於 11 及 小於 6 時才會去增加 quality
但因為重構後是先扣除銷售日期,所以 sell In 判斷應該減為 10 跟 5 才對

這可能是另一個議題是重構前單元測試要寫到多仔細,邊界測試可能要寫不夠滿嗎 QQ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment