Modern jQuery: Refactoring and Testing the Way Forward


Ken Dale / @kendaleiv

https://trends.builtwith.com/javascript/jQuery

Rewrites

https://www.google.com/search?q=should+i+rewrite

Opinions, choose your own adventure, YRMV, etc.

</meta>

Goal: Betterify this


$(document).ready(function () {
  $('#fetch').click(function () {
    var stockSymbol = $.trim($('#stock-symbol').val().toUpperCase());

    $.ajax('some_long_url', {
      beforeSend: function() {
        $('#current-stock-price').html('Loading ...');
      },
      success: function (res) {
        var quote = $(res).find('[symbol="' + stockSymbol + '"]');
        var lastTradePrice = quote.find('LastTradePriceOnly').text();

        $('#current-stock-price').html('' + stockSymbol + ': $' + lastTradePrice + ' retrieved at ' + new Date().toString());
        $('#stock-price-log').html($('#stock-price-log').html() + '
  • ' + stockSymbol + ' $' + lastTradePrice + ' retrieved at ' + new Date().toString() + '
  • '); } }); }); });
    View on GitHub

    Why?

    Maintainability, testability, warm fuzzies

    External files, strict mode, IIFE





    import { something } from 'somewhere';

    Continue using jQuery

    Or, switch to something else




    (fetch, etc.)

    You decide

    Prepare Yourself






    Refactoring ahead

    
    $.ajax(url).done(function (res) {
      var quote = $(res).find('[symbol="' + stockSymbol + '"]');
      var lastTradePrice = quote.find('LastTradePriceOnly').text();
    
      // More code not related to getting the lastTradePrice
    });
              
    
    // data-provider.js
    import $ from 'jquery';
    
    export default class DataProvider {
      getStockPrice(stockSymbol) {
        if (!stockSymbol) {
          throw new Error('Must provide a stockSymbol');
        }
    
        const url = 'http://query.yahooapis.com/v1/public/yql'
          + `?q=select * from yahoo.finance.quotes where symbol in ("${stockSymbol}")`
          + '&diagnostics=true'
          + '&env=http://datatables.org/alltables.env';
    
        return fetch(url)
          .then(res => res.text())
          .then(text => {
            const quote = $(text).find(`[symbol="${stockSymbol}"]`);
            const lastTradePrice = quote.find('LastTradePriceOnly').text();
    
            return lastTradePrice;
          });
      }
    }
    
    // Usage:
    new DataProvider().getStockPrice('MSFT').then(lastTradePrice => {
      /* Code here */
    });
              

    Lather, rinse, repeat


    If you want to explore further refactoring
    examine start and finish.

    Start the app

    Single obvious way to make it work.

    
    import StockRetriever from './stock-retriever';
    import DataProvider from './data-provider';
    import UiProvider from './ui-provider';
    
    new StockRetriever(new DataProvider(), new UiProvider())
      .init();
              

    Testing

    Add tests while you refactor

    Testing with AVA

    
    import test from 'ava';
    
    test('simple test', t => {
      t.true(true);
    });
              

    Jasmine is nice, too!

    AVA: Promises

    
    import test from 'ava';
    
    test('should return stock price', t => {
      return new DataProvider()
        .getStockPrice('TEST')
        .then(lastTradePrice => {
          t.is(lastTradePrice, '123.45');
        });
    });
              

    fetch-mock

    
    import test from 'ava';
    import fetchMock from 'fetch-mock';
    
    import DataProvider from '../src/data-provider';
    
    test.beforeEach(() => {
      const response = `
    123.45`;
    
      fetchMock.get('*', response);
    });
    
    test.afterEach(() => {
      fetchMock.restore();
    });
    
    test('should return stock price', t => {
      return new DataProvider()
        .getStockPrice('TEST')
        .then(lastTradePrice => {
          t.is(lastTradePrice, '123.45');
        });
    });
              
    AVA: Using jQuery with the DOM
    
    // https://github.com/avajs/ava/blob/20ab39de046e527dec7b369f375d6c8e5fd4f5e1/docs/recipes/browser-testing.md#setup-jsdom
    
    global.document = require('jsdom').jsdom('');
    global.window = document.defaultView;
    global.navigator = window.navigator;
              

    Code


    Start

    Finish

    Surprise!


    You just learned a language agnostic skill!

    Quick recap


    • Refactor into modules (or, any modular approach).
    • Make decisions along the way.
    • Add tests, too!





    Congratulations: You've reached the next level!

    https://github.com/kendaleiv/jquery-js-refactor




    Questions?

    Ken Dale / @kendaleiv