基于Virtual DOM與Diff DOM的測試代碼生成

jopen 8年前發布 | 23K 次閱讀 DOM 前端技術


盡管是在年末,并且也還沒把書翻譯完,也還沒寫完書的第一稿。但是,我還是覺得這是一個非常不錯的話題——測試代碼生成。

當我們在寫一些UI測試的時候,我們總需要到瀏覽器去看一下一些DOM的變化。比如,我們點擊了某個下拉菜單,會有另外一個聯動的下拉菜單發生了變化。而如果這個事件更復雜的時候,有時我們可能就很難觀察出來他們之間的變化。

Virtual DOM

盡管這里的例子是以Jasmine作為例子,但是我想對于React也會有同樣的方法。

一個Jasmine jQuery測試

如下是一個簡單的Jamine jQuery的測試示例:

  describe("toHaveCss", function (){
    beforeEach(function (){
      setFixtures(sandbox())
    })

    it("should pass if the element has matching css", function (){
      $("#sandbox").css("display", "none")
      $("#sandbox").css("margin-left", "10px")
      expect($("#sandbox")).toHaveCss({display: "none", "margin-left": "10px"})
    })
});

在beforeEach的時候,我們設定了固定的DOM進去,按照用戶的行為做一些相應的操作。接著依據這個DOM中的元素變化 ,來作一些斷言。

那么,即使我們已經有一個固定的DOM,想要監聽這個DOM的變化就是一件容易的事。在我們斷言之前,我們就會有一個新的DOM。我們只需要Diff一下這兩個DOM的變化,就可以生成這部分測試代碼。

virtual-dom與HyperScript

在尋覓中發現了 virtual-dom 這個庫,一個可以支持創建元素、diff計算以及patch操作的庫,并且它效率好像還不錯。

virtual-dom可以說由下面幾部分組成的:

  1. createElement,用于創建virtual Node。
  2. diff,顧名思義,diff算法。
  3. h,用于創建虛擬樹的DSL——HyperScript。HyperScript是一個JavaScript的HyperText。
  4. patch,用于patch修改的內容。

舉例來說,我們有下面一個生成Virtual DOM的函數:

function render(count)  {
    return h('div', {
        style: {
            textAlign: 'center',
            lineHeight: (100 + count) + 'px',
            border: '1px solid red',
            width: (100 + count) + 'px',
            height: (100 + count) + 'px'
        }
    }, [String(count)]);
}

render函數用于生成一個Virtual Node。在這里,我們可以將我們的變量傳進去,如1。就會生成如下圖所示的節點:

{
    "children": [
        {
            "text": "1"
        }
    ],
    "count": 1,
    "descendantHooks": false,
    "hasThunks": false,
    "hasWidgets": false,
    "namespace": null,
    "properties": {
        "style": {
            "border": "1px solid red",
            "height": "101px",
            "lineHeight": "101px",
            "textAlign": "center",
            "width": "101px"
        }
    },
    "tagName": "DIV"
}

其中包含中相對應的屬性等等。而我們只要調用createElement就可以創建出這個DOM。

如果我們修改了這個節點的一些元素,或者我們render了一個count=2的值時,我們就可以diff兩個DOM。如:

virtualDom.diff(render(2), render(1))

根據兩個值的變化就會生成如下的一個對象:

{
    "0": {
        "patch": {
            "style": {
                "height": "101px",
                "lineHeight": "101px",
                "width": "101px"
            }
        },
        "type": 4,
        "vNode": {
            ...
        }
    },
    "1": {
        "patch": {
            "text": "1"
        },
        "type": 1,
        "vNode": {
            "text": "2"
        }
    },
    ...
}

第一個對象,即0中包含了一些屬性的變化。而第二個則是文本的變化——從2變成了1。我們所要做的測試生成便是標記這些變化,并記錄之。

標記DOM變化

由于virtual-dom依賴于虛擬節點vNode,我們需要將fixtures轉換為hyperscript。這里我們就需要一個名為html2hyperscript的插件,來解析html。接著,我們就可以diff轉換完后的DOM:

var leftNode = "", rightNode = "";
var fixtures = '<div id="example"><h1 class="hello">Hello World</h1></div>';
var change = '<div id="example"><h1 class="hello">Hello World</h1><h2>fs</h2></div>';
parser(fixtures, function (err, hscript) {
  leftNode = eval(hscript);
});

parser(change, function (err, hscript) {
  rightNode = eval(hscript);
});

var patches = diff(leftNode, rightNode);

接著,我們需要調用patch函數來做一些相應的改變。

luffa.patch(virtualDom.create(leftNode), patches)

并且,我們可以嘗試在patch階段做一些處理——輸出修改:

function printChange(originRootNodeHTML, applyNode) {
  var patchType;

  for (var patchIndex = 0; patchIndex < applyNode.newNodes.length; patchIndex++) {
    patchType = applyNode.newNodes[patchIndex].method;
    switch (patchType) {
      case 'insert':
        printInsert(applyNode);
        break;
      case 'node':
        printNode(applyNode, originRootNodeHTML, patchIndex);
        break;
      case 'remove':
        printRemove(applyNode, originRootNodeHTML, patchIndex);
        break;
      case 'string':
        printString(applyNode, originRootNodeHTML, patchIndex);
        break;
      case 'prop':
        printProp(applyNode, originRootNodeHTML, patchIndex);
        break;
      default:
        printDefault(applyNode, originRootNodeHTML, patchIndex);
    }
  }
}

根據不同的類型,作一些對應的輸出處理,如pringNode:

function printNode(applyNode, originRootNodeHTML, patchIndex) {
  var originNode = $(applyNode.newNodes[patchIndex].vNode).prop('outerHTML') || $(applyNode.newNodes[patchIndex].vNode).text();
  var newNode = $(applyNode.newNodes[patchIndex].newNode).prop('outerHTML');

  console.log('%c' + originRootNodeHTML.replace(originNode, '%c' + originNode + '%c') + ', %c' + newNode, luffa.ORIGIN_STYLE, luffa.CHANGE_STYLE, luffa.ORIGIN_STYLE, luffa.NEW_STYLE);
}

用Chrome的console來標記修改的部分,及添加的部分。

最后,我們似乎就可以生成相應的測試代碼了。。。

其他

源碼見: https://github.com/phodal/luffa

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!