Chapter 7. Test, package, and document your plugin

published book

This chapter covers

  • Testing your plugin
  • Packaging your plugin for distribution
  • Documenting and demonstrating your plugin

Once you’ve written your new plugin, you probably want to make it available to the wider jQuery community. To give it the best chance of competing with other plugins that provide similar functionality, you should ensure that it works as expected in all situations. Using a testing suite such as QUnit lets you create a series of repeatable tests for a wide range of scenarios for your plugin, in the familiar environment of a web browser.

You should also provide potential users with a package that includes everything they need to implement your plugin. As well as the plugin code itself, you may need to include associated stylesheets, images, localizations, and perhaps even a simple demonstration page. To reduce network requirements when the plugin is being used, it’s also helpful to include a minimized version of your plugin code, as provided by one of several online packing tools. All of the related files are then collected into a single archive file for ease of distribution.

Finally, you need to document your plugin thoroughly so your users know what to expect and can adapt it to their varying needs. Describe each option that can be set, each callback that can be registered, and each method that can be invoked. You should also show off your plugin’s abilities with a demonstration page, preferably with corresponding code snippets that can be easily copied and applied by a user.

The sections in this chapter address each of these issues to help you publish a plugin that works correctly and can be easily implemented by end users.

7.1. Testing your plugin

Testing your plugin may seem like an obvious requirement, but doing it properly can be an art form. Even with only a few options available to change your plugin’s behavior, the possible combinations quickly grow and can become unwieldy.

Initially, you might start with a simple page and adjust options manually to check their functionality, but as the complexity of the plugin increases, this is no longer practical and leads to inconsistent testing of various situations. A set of repeatable unit tests overcomes this problem, allowing you to easily run a full suite of tests without missing anything.

A standard set of tests (following the create a repeatable test case suite principle) also lets you refactor your code more easily, as you can confirm after each change that the plugin still works as expected.

This section looks at what you should be testing, and then at how you’d implement this using the QUnit testing suite. As an example, you’ll create unit tests for the MaxLength plugin you created in chapter 5.

7.1.1. What to test?

Ideally, you should test everything—all methods for all combinations of options, applied to varying elements in diverse positions upon the page, in all the major browsers (one of the guiding principles). But this isn’t practical for most plugins, so concentrate on testing each option or method separately or in small groups of related settings.

Start with the basics of setting default values for all plugin instances ($.pluginname.setDefaults), followed by the setting and retrieval of individual or groups of options ($(selector).pluginname('option')...). Next, test the instantiation ($(selector).pluginname()) and destruction ($(selector).pluginname ('destroy')) of your plugin to ensure that it makes the necessary DOM modifications and then removes them completely. If your plugin can be enabled and disabled, then test that this happens as expected ($(selector).pluginname('disable')). Check that other operations don’t work when the plugin is in its disabled state.

Test each option individually to check that it correctly affects the targeted elements. For options that accept different types of values, include tests for each of these types separately. For options that are event callbacks, add tests for each one and ensure that the parameters passed to it are correct.

For each method and utility function offered by the plugin, test that it operates as expected. Check that chaining occurs for methods that don’t return a special value from the plugin.

Test user interactions with the elements affected by the plugin using jQuery’s event methods. Trigger a normal, bubbling event (one that’s passed on to the containing elements up through the DOM hierarchy) with $(selector).trigger('eventname'). To invoke an event without bubbling, use $(selector).triggerHandler('eventname') instead. You can also use the named jQuery event functions to initiate events, such as $(selector).click(). If you need to pass additional information with the event, such as the position of a mouse click or the key pressed, use the trigger function but pass it an Event object instead of only the event name.

var e = $.Event('click');
e.pageX = 10;
e.pageY = 20;
$(selector).trigger(e);

7.1.2. Using QUnit

QUnit is a JavaScript test suite developed by John Resig and now maintained by the jQuery team (http://qunitjs.com/). It’s used by the jQuery and jQuery UI teams to test jQuery and jQuery UI themselves, and can be applied to any JavaScript code. A QUnit page contains a suite of tests that you can easily run to verify the functionality of your plugin.

To run a QUnit test, you start with an HTML template as shown in listing 7.1. You load in the QUnit CSS and JavaScript, and add your tests in a separate script element. In the body of the page, you add two specific divs—one to hold the test results (#qunit) and the other to hold any elements needed by the tests themselves (#qunit-fixture). The latter is hidden by moving it off the screen, thus allowing its contents to be “visible” while not affecting the display of the test outcome. Figure 7.1 shows the results of loading this sample page—a failed test, because the supplied value doesn’t match the expected one.

Figure 7.1. Running the QUnit basic example
Listing 7.1. QUnit page template

Your test code consists of one or more calls to the test function, each of which contains a set of related assertions (statements of expected results). Each section includes code to set up the appropriate environment and then one or more assertions regarding the outcome of that test. Each time a test is invoked, the test environment is re-established, with the contents of the #qunit-fixture div being restored to their original state, protecting tests from each other and allowing each to start from a known state.

Within the page header is the page title, a status bar colored either green indicating a successful test run or red indicating some sort of failure, the current browser identification, and various checkboxes to change the test behavior. The body of the document shows the results of running the tests. By default, tests that pass are collapsed, whereas those that fail are expanded to show individual assertions. You can toggle a test by clicking on its header. Click Rerun or double-click a test header to rerun just that test.

You can also filter individual tests by matching a portion of their name (ignoring case) as a parameter to the test page, such as

test.html?filter=basic

Alternately, you can exclude tests matching a filter by prefixing the value with an exclamation mark (!):

test.html?filter=!event

Checking the Check for Globals checkbox in the header reruns the tests but throws an error if any global variables are declared. Use this option to monitor the global namespace and assist in preventing interference with other libraries. Checking the No Try-Catch checkbox reruns the tests but doesn’t trap any exceptions that may be raised, allowing them to fall through to the browser and stop the tests at that point. Normally exceptions are caught and appear as failure messages within the output.

Run your test page on all the major browsers to ensure compatibility across all of these platforms.

To illustrate how you’d test a plugin, you can create a test suite for the MaxLength plugin from chapter 5.

7.1.3. Testing the MaxLength plugin

To ensure that the MaxLength plugin works as expected, you should write a QUnit test suite to validate it.

For this plugin, you start off testing the setDefaults function. After checking that the initial value of a default option is as expected, you call the function to change that value, and check it again to confirm the update. A message provided to the checking call is logged as is on success, and on failure is concatenated with the unequal values being compared. Figure 7.2 shows the (successful) result of running this initial test; the next listing shows the initial page setup and the code behind this first test.

Figure 7.2. Running the first MaxLength test
Listing 7.2. Setting up MaxLength tests

The testing page starts by loading in the QUnit styling and code , followed by jQuery and the plugin code .

The body of the page contains the two standard divs: the first (#qunit) to hold the QUnit interface and the results of running the tests and the second (#qunit-fixture) to hold any elements required by the tests themselves . The latter is cloned and re-created at the beginning of each set of tests to provide a clean environment for those tests, and is styled to move it out of the normal viewport so that its contents remain “visible” without overlaying the QUnit results.

The test appears within a call to the QUnit test function . This function defines a name for the set of assertions that it contains and a callback function to run them. A test should be targeted at a particular aspect of the plugin, and may be run on its own to focus on that one area. Any changes you make to the environment during a test will apply until that test is completed.

To ensure that any code failures are correctly handled, you should start each test with a call to the expect function, to specify how many assertions are being made within this set . This way, if a problem arises that prevents the remaining code from executing, you’ll still be informed of an error (instead of it failing silently) because the number of assertions run won’t match what was expected. Alternatively, you can supply the number of expected assertions as the second parameter to the test function, pushing the callback function into third position. Finally, you should initialize the environment for your test, run the code, and make assertions about the outcome .

When testing the setDefaults function, you call the function to alter the value and check that it did in fact change. The calls to equal are assertions provided by QUnit. Each one compares the actual value with an expected value (the first two parameters) and succeeds if these are equal (after type conversion, if necessary). A descriptive message is provided to the call as its third parameter for logging purposes.

The init function in the code initializes the textarea within the #qunit-fixture div by resetting its value, removing any existing MaxLength functionality, and then adding it back in again. Although it’s not necessary for this particular test, which relates to global functionality, the init function will be used by later sets of tests. The input field and span within the fixture div will be used to contain feedback information in later tests.

7.1.4. Testing option setting and retrieval

Having checked the setting of default options for the plugin, you can continue by testing the setting and retrieval of options. You can set individual options, or collections of them, in the one call. Similarly you can retrieve one or all option values from a call. All of these possibilities should be tested, as shown in the following additional test.

Listing 7.3. Testing MaxLength options

Define a new test to probe the option capabilities of the plugin . As done previously, specify the number of assertions to be made in this test so you’re sure you’re not missing any .

Initialize the test elements with a call to init , and then confirm that the initial state is as expected. A call to the option method without any other parameters results in an object being returned containing all the current option values. Use the deepEqual function provided by QUnit to compare the returned value with the expected one . This function differs from equal by comparing each attribute of the two objects separately (and recursively if necessary), rather than only checking that the two objects are the same one. Use the equal function to check a single simple option value retrieved when using the option method with a named option .

Continue the test by changing an option value, again with the option method , and check for the changes that should have resulted. Also test the situations where several option values are changed at the one time by providing a collection of new values to the option method , and where an individual option is altered by providing its name and new value .

7.1.5. Simulating user actions

The behavior of the MaxLength plugin depends on interactions with the user, in particular their entry of text into the affected textarea. You need to test this behavior by simulating these exchanges. Other plugins may need to test mouse clicks or drags. The following listing shows how the MaxLength tests handle this requirement.

Listing 7.4. Testing text entry

As in the earlier tests, you define the test and give it a name . The usual setting of the expected number of assertions and field initialization follow. Then enter text as though it had been typed via the keyboard and check the resulting content of the field . You can also make assertions about the state of the plugin by using the ok function provided by QUnit . This function takes a Boolean value as its first parameter and asserts it to be true. In this case, you test that certain marker classes haven’t yet been applied to the textarea.

Having run through a series of actions and assertions, you reinitialize the textarea with a different option setting and rerun the tests to observe the change in behavior .

Two helper functions assist in the simulation of events normally triggered by the user. The keyboard function generates keydown, keypress, and keyup events for each character in the given string, whereas the backspace function does the same for a single backspace character. Both use the Simulate plugin (https://github.com/eduardolundgren/jquery-simulate) to send the events.

It’s easy to replicate most other user interactions with elements on the page via jQuery’s standard event handler functions. You can simulate a mouse click by calling click on that element, or via the trigger function (for all matched elements with event bubbling) or triggerHandler function (for just the first element without any bubbling).

$('#button1').click();
$('#button1').triggerHandler('click');

7.1.6. Testing event callbacks

Many plugins use event callbacks to notify the user of significant events within the plugin, such as values changing or timeouts expiring. The conditions triggering these events and the content of any parameters provided to the callback should be tested. In the MaxLength plugin, an event is triggered when the textarea reaches or exceeds its allowed limit. This listing shows the event callback tests.

Listing 7.5. Testing callbacks

Start by declaring some variables to track the callback invocations and the actual function to be called . You can use a simple counter to record how often the callback is invoked and another variable to capture its single parameter.

Define the new test as before , set the expected number of assertions, and initialize the test elements. You can make some initial assertions to ensure that the callback isn’t triggered before it should be . Then perform the triggering action and verify that the callback was called and received the expected parameter value .

Finally, reinitialize the test environment, change an option and check the callback behavior under the new conditions .

The complete test page for the MaxLength plugin is available for download from the book’s website. As well as the tests described earlier, it includes tests for enabling and disabling the plugin, for removing its functionality, and for presenting feedback.

You’ve seen how to create a unit test suite for the MaxLength plugin, using the abilities of the QUnit package. But QUnit offers much more than was shown in this chapter. Additional features include grouping tests into modules, running asynchronous tests, other assertions, and event hooks to monitor QUnit’s progress. For more information on its capabilities, read the QUnit API documentation (http://api.qunitjs.com/) and the “Introduction” and “Cookbook” articles on the main web-site (http://qunitjs.com/).

7.2. Packaging your plugin

Now that you’re satisfied your plugin works correctly in various scenarios, you want to make it available to the wider jQuery community. To do this, you should package up everything that the plugin requires to make it simple to distribute and easy for a potential user to obtain.

You need to collect all the relevant files, create a minimized version of your code to reduce download times, and provide a simple implementation of your plugin to start users off. Then combine all these files in a single archive for a one-step download. Each of these steps is described in this section.

7.2.1. Collating all the files

Often a complete plugin consists of more than just a JavaScript file—you may also have any or all of the following:

  • Additional JavaScript modules for less-used functionality
  • Minimized versions of the JavaScript modules (see section 7.2.2)
  • Localization files to adapt your plugin to other languages and countries
  • CSS files to style your plugin in various ways
  • Image and other resource files that are used by the CSS or that may be used via options of the plugin
  • A basic example of how to use the plugin (see section 7.2.3)
  • Documentation on the plugin (see section 7.3)

As an example, my Datepicker plugin has all of the types of files listed here, except the documentation, which is available separately. The file list shown in figure 7.3 identifies the various components.

Figure 7.3. Files that make up the Datepicker plugin

Place all of your files in a single zip archive. As well as reducing the size of the package through compression, the archive keeps all the required files together in one package, making it easier to distribute without forgetting anything.

7.2.2. Minimizing your plugin

To reduce your plugin’s download requirements, you can make it smaller by removing unnecessary text, such as comments and whitespace. This process is known as minimizing the code.

By including both the original source code and the minimized version in your download, you make it easy for potential users to use whichever suits their needs—debugging or learning with the full code or production use with the minimized one. The minimized version should be named the same as your original plugin file, but with a .min addition after the plugin name; for example, jquery.maxlength.min.js.

Once you’ve minimized the code itself, you should copy over the header comments from the original code, because these identify the plugin, its version, and author, and should provide a URL for the plugin website so that users can find updates, examples, and documentation.

Several websites offer to minimize your JavaScript code, including the following:

Dean edwards’ packer

Dean Edwards’ Packer is available online and lets you generate the standard minimized code by removing comments and whitespace. It also lets you generate a Base62 encoded file, which is usually smaller than the minimized code, but requires additional processing on the client to reconstruct the original code. The Base62 version also doesn’t compress further as much as the straight minimized version, so you get better performance from the minimized code in conjunction with a gzip filter on your server. In both cases, you can opt to shrink variable names. In addition to further reducing the file size, this setting provides a measure of code obfuscation.

To use Dean Edwards’ Packer, open the website, paste your code into the top panel, select your options, and click the Pack button, as shown in figure 7.4. Then copy the resulting code from the panel at the bottom and save it locally.

Figure 7.4. Dean Edwards’ Packer in action
Yui compressor

YUI Compressor is available as a Java (1.4+) application that you download and run locally. This makes it possible to include the minimization step in a local build process. It analyzes the source code via the Rhino JavaScript implementation in Java, and then rewrites it while omitting comments and unessential whitespace, at the same time replacing internal variable names with shorter values.

>java -jar \path\to\yuicompressor-2.4.7.jar -o jquery.maxlength.min.js
   jquery.maxlength.js
Google closure compiler

The Google Closure Compiler takes a different approach. In addition to removing unnecessary comments and whitespace, it can scan the code using compiler-like analysis with the aim of rewriting it for equivalent functionality with less code. As a byproduct, it also generates warnings and error messages for (potential) problems found while parsing the code. The compiler is available as an online tool (see figure 7.5) or for download as a Java application (http://closure-compiler.appspot.com/home).

Figure 7.5. Google Closure Compiler in action

When using the former, paste your code in the box on the left, select your options, and click Compile. Copy the resulting code from the box on the right and save it locally.

Closure Compiler options

The Closure Compiler offers three levels of optimization. The basic level is Whitespace Only, which removes comments, line breaks, unnecessary spaces, and other whitespace. The next level is Simple, which removes whitespace as in the previous option and renames internal variables to use shortened names. Both of these options are safe to use on any code, provided you don’t access local variables via string names.

The third level is Advanced, which performs the previous optimizations and then examines the code to determine whether it can be rewritten to achieve the same result. This level requires that your code conform to certain assumptions that the compiler makes, and the resulting code may not run if these aren’t adhered to. See Google’s “Closure Compiler Compilation Levels” documentation (https://developers.google.com/closure/compiler/docs/compilation_levels) for more details.

When checked, the Pretty Print option adds back line breaks and indents to make the code human-readable again, although this does increase the file size a little. When checked, the Print Input Delimiter option adds a comment into the output indicating where each of multiple input files starts.

Comparison

As a comparison, applying these three tools to the MaxLength plugin results in the savings shown in table 7.1. There’s not a lot of difference between them for this plugin, particularly when further zipped, but the Google Closure Compiler does come out on top.

Table 7.1. Comparing minimization implementations

Product

Minimized size (bytes)

% saving

After zip (bytes)

% saving

Dean Edwards’ Packer 5238 53.4% 1551 86.2%
YUI Compressor 5192 53.8% 1566 86.1%
Google Closure Compiler 4949 56.0% 1497 86.7%

7.2.3. Providing a basic example

Try to include a complete basic example of your plugin’s operations alongside the code and other supporting files. Such an example demonstrates that the plugin works before users start experimenting with changing options and invoking methods. The page should be able to be run as soon as your distribution package is unpacked.

Keep the page as minimal as possible to reduce confusion as to what’s required to use your plugin. You should load jQuery (and jQuery UI if applicable) from a CDN to avoid having to include the jQuery library in your download or worry about where jQuery might be kept in relation to this page. Show the default configuration for your plugin in this page, and allow users to add options later as they explore the plugin’s possibilities. Include a link to your plugin’s main demonstration page and any documentation on the plugin’s abilities.

The basic page for the MaxLength plugin is shown in the following listing.

Listing 7.6. A minimal MaxLength page

Start by loading any CSS required by your plugin followed by the jQuery library and your plugin code . Invoke your plugin with a minimum of configuration . To further assist your prospective users, include links to the documentation for the plugin and to the plugin’s website that demonstrates more of its abilities . Finally, include the elements upon which your plugin operates , again in their simplest form.

jQuery content delivery network

Google provides a CDN that contains multiple versions of jQuery, both in full source and in minimized format. Change the version number as necessary, and omit the .min if you want the full source code:

<script type="text/javascript" src="http://ajax.googleapis.com/
   ajax/libs/jquery/1.8.2/jquery.min.js">

It also hosts jQuery UI versions:

<script type="text/javascript" src="http://ajax.googleapis.com/
   ajax/libs/jqueryui/1.9.2/jquery-ui.min.js">

It even provides jQuery UI ThemeRoller themes. Change the version number and theme name as required: <link type="text/css"

<link type="text/css"
   rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/
   jqueryui/1.9.2/themes/south-street/jquery-ui.css">

Microsoft and jQuery (via MediaTemple) also provide CDNs for jQuery, jQuery UI, and the standard ThemeRoller themes.

7.3. Documenting your plugin

Your plugin may be fantastic and highly configurable, but if users don’t know about its abilities, they won’t be able to use it to its fullest extent. Most developers include some comments within the code (at least they should), and the plugin framework does collect all the available options together, but users don’t want to have to wade through the code to find those descriptions.

By documenting your plugin and publishing it on the web, you make it easy for users to evaluate the possibilities offered by your plugin, and then to configure the plugin for their own use. Clear documentation with examples can reduce your maintenance burden, as users can find the answers to many of their questions without having to contact you directly.

7.3.1. Documenting options

All the options available to configure your plugin need to be documented. Each one should list the option’s name, its expected type or types, its default value, and an explanation of its purpose and effect. Figure 7.6 shows documentation for some options of the MaxLength plugin.

Figure 7.6. Documentation for some MaxLength options

For more complicated options, especially those that accept objects or functions, you should provide a code snippet to illustrate how that option could be used. List each of the attributes of an object value, along with its expected type, default value, and description. Similarly, list each parameter passed to a function value with its expected type and purpose. For a function, you should also detail what this refers to within the body of the function, and what return value, if any, is expected back.

Provide links to related options as appropriate when describing interactions between them. If the set of possible options is extensive, consider providing an alphabetical list of links at the top and/or bottom of the page to provide quick access to particular settings.

As your plugin evolves over time, record in which version an option was added or changed. This will enable users of older versions to easily see what to do when upgrading to the latest version, or provide a reference if they want to continue using their existing version.

7.3.2. Documenting methods and utility functions

Any methods recognized by your plugin also need to be documented, along with any utility functions provided by it.

Each method or function should detail the manner in which it’s invoked, showing all the parameters as well as the value returned by the call and a description of its purpose. Be sure to note any methods that don’t return the jQuery object and so can’t be used for further chaining. Figure 7.7 shows documentation for the first few functions and methods of the MaxLength plugin.

Figure 7.7. Documentation for some MaxLength functions and methods

List each parameter and indicate its expected type, its optionality, any default value, and its purpose. Provide examples of how to invoke each function or method. Include links to other methods or back to options as appropriate, and note in which version items were introduced or changed.

7.3.3. Demonstrating your plugin’s abilities

First impressions count for a lot, so present your plugin in the best possible light by providing a web page that demonstrates most, if not all, of its abilities.

First, show the plugin in its default configuration, and then add examples of customizing it by setting various options. Where possible, include the code that you run to produce these examples, allowing users to find a sample that is close to what they want and to copy the relevant script to achieve that.

As well as serving as a showpiece and a repository of examples for potential users, your demonstration page also provides a valuable testing tool for your plugin, especially its visual aspects that are difficult to test with an automated tool. Open the page and put it through its paces on all the major browsers.

You can also include any or all of the following:

  • Instructions on how to implement the plugin on the user’s page
  • Feedback from other users
  • A list of websites that already use the plugin
  • A quick reference to all the options available
  • A link to more detailed documentation for the plugin
  • Access to localizations for the plugin (with appropriate credits)
  • A list of previous versions and the changes therein

Figure 7.8 shows the main part of the demonstration page for the MaxLength plugin.

Figure 7.8. Demonstrating the MaxLength plugin
What you need to know

Having a unit test suite for a plugin helps ensure that it works correctly in most situations and browsers.

QUnit provides a JavaScript unit test framework that allows you to comprehensively test your plugin.

Package all the files for your plugin into one archive for ease of distribution.

Provide minimized versions of your code in your distribution package to reduce network traffic for potential users.

Document your plugin’s options and methods to assist users in customizing it for their own needs.

Provide demonstrations of your plugin’s functionality, preferably with code examples, to help convince users to try it.

Try it yourself

Write tests for the Watermark plugin you developed as an exercise in chapter 5, following the pattern of the MaxLength tests shown in this chapter. Make sure you test the option to change where the label text comes from and the clear method to erase a label.

7.4. Summary

It’s all well and good to have written the best plugin ever, but if users can’t easily find, deploy, and configure it, they may bypass it for another offering.

Users expect the plugin to be (relatively) bug free when it’s made publicly available. To assist in your testing efforts, the QUnit package enables you to write repeatable unit tests for your code. Try to test every option, method, and function offered by your plugin. Having a set of repeatable tests makes it easier to refactor your plugin code while ensuring that it continues to work as expected.

Package all of the plugin files into a single zip archive to make it easier to distribute and to ensure that nothing gets missed. Provide a minimized version of the plugin code to assist users in reducing the download requirements of your code.

A demonstration page for the plugin serves as a visual test bed for your own use, and it highlights all of the possibilities offered by your plugin. Include code samples to give users a helping hand toward producing similar effects in their own websites.

Document everything so that users know the full range of the plugin’s abilities and what to expect when they configure it or interact with it. Describe all of the configuration options available, including event handlers, all of the methods that the plugin recognizes, and any other functions that the plugin offers. Well-written and comprehensive documentation can reduce your maintenance burden as users can often find the answers to problems themselves.

In the next part of the book, you’ll see how the jQuery UI package offers its own plugin framework and how to tap into that to write compatible plugins. This chapter on testing, packaging, and documenting your plugin applies to any plugins you produce.

sitemap

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage