diff --git a/README.markdown b/README.markdown index 5977992..bdc4943 100644 --- a/README.markdown +++ b/README.markdown @@ -1,32 +1,23 @@ Jasmine ======= -**YET ANOTHER JavaScript testing framework** +**A JavaScript Testing Framework** Quick Start ---------- ### Ruby Suite Running - sudo gem sources -a http://gems.github.com - sudo gem install geminstaller - git clone git://github.com/pivotal/jasmine.git - cd jasmine - sudo geminstaller - cd examples/ruby - rake jasmine_server - -open `http://localhost:8888/` in your favorite browser. +Please use the [jasmine-ruby gem](http://github.com/pivotal/jasmine-ruby) to run suites in a ruby environment. ### HTML Suite Running + [Get the latest release from the downloads page](http://github.com/pivotal/jasmine/downloads) - git clone git://github.com/pivotal/jasmine.git - -open `examples/test/html/example_suite.html` in your favorite browser. +Open `example/example_runner.html` in your favorite browser. ### Automatic Suite Running (w/ Selenium) sudo gem sources -a http://gems.github.com - sudo gem install geminstaller + sudo gem install geminstaller git clone git://github.com/pivotal/jasmine.git cd jasmine sudo geminstaller @@ -35,8 +26,7 @@ open `examples/test/html/example_suite.html` in your favorite browser. Releases ---------- -0.10.0 [[download]](http://github.com/pivotal/jasmine/zipball/master) -`git clone git://github.com/pivotal/jasmine.git` +0.10.0 [[download]](http://cloud.github.com/downloads/pivotal/jasmine/jasmine-0.10.0.zip) 0.9.0 [[download]](http://github.com/pivotal/jasmine/zipball/0.9.0) @@ -51,47 +41,59 @@ Pull Requests We welcome your contributions! Jasmine is currently maintained by Davis Frank ([infews](http://github.com/infews)), Rajan Agaskar ([ragaskar](http://github.com/ragaskar)), and Christian Williams ([Xian](http://github.com/Xian)). You can help us by removing all other recipients from your pull request. -Why Another Frickin' JS TDD/BDD Framework? +Why Another JavaScript TDD/BDD Framework? ----------- -There are some situations when you want to test-drive JavaScript, but you don't want to be bothered with or even have an explicit document. You have no DOM to work with and thus lack HTML elements on which to hang event handlers. You may need to make asynchronous calls (say, to an AJAX API) and cannot mock/stub them. +There are some great JavaScript testing frameworks out there already, so why did we write another? -But you still need to write tests. +None of the existing frameworks quite worked the way we wanted. Many only work from within a browser. Most don't support testing asynchronous code like event callbacks. Some have syntax that's hard for JS developers or IDEs to understand. -What's an Agile Engineer to do? +So we decided to start from scratch. Enter Jasmine ------------ -Jasmine is yet another JavaScript testing framework. It's *heavily* influenced by JSSpec, ScrewUnit & [JSpec](http://github.com/visionmedia/jspec/tree/master), which are all influenced by RSpec. But each of those was lacking in some way: JSSpec & ScrewUnit require a DOM. JSpec's DOM-less assumption was a great start, but it needed asynchronous support. +Jasmine is our dream JavaScript testing framework. It's heavily influenced by, and borrows the best parts of, ScrewUnit, JSSpec, [JSpec](http://github.com/visionmedia/jspec/tree/master), and of course RSpec. -So we started over. And TDD'd a whole new framework. Enjoy. +Jasmine was designed with a few principles in mind. We believe that a good JavaScript testing framework: + +* should not be tied to any browser, framework, platform, or host language. +* should have idiomatic and unsurprising syntax. +* should work anywhere JavaScript can run, including browsers, servers, phones, etc. +* shouldn't intrude in your application's territory (e.g. by cluttering the global namespace). +* should play well with IDEs (e.g. test code should pass static analysis). + +Some of our goals while writing Jasmine: + +* it should encourage good testing practices. +* it should integrate easily with continuous build systems. +* it should be simple to get started with. + +The result is Jasmine, and we love test-driving our code with it. Enjoy. How To ------ -There is a nice example of how to use Jasmine in the /example directory. But here's more information. - -Exciting changes are afoot and many syntax changes have been made to make Jasmine more usable. Please read the examples below for updates. +There is a simple example of how to use Jasmine in the /example directory. But here's more information. ### Specs Each spec is, naturally, a JavaScript function. You tell Jasmine about this spec with a call to `it()` with a string and the function. The string is a description that will be helpful to you when reading a report. it('should be a test', function () { - var foo = 0 + var foo = 0; foo++; }); ### Expectations -Within your spec you will want/need to make expectations. These are made with the `expect()` funciton and expectation matchers. like this: +Within your spec you will want to express expectations about the behavior of your application code. These are made with the `expect()` function and expectation matchers, like this: it('should be a test', function () { - var foo = 0 - foo++; + var foo = 0; // set up the world + foo++; // call your application code - expect(foo).toEqual(1); + expect(foo).toEqual(1); // passes because foo == 1 }); Results of the expectations are logged for later for reporting. @@ -100,166 +102,71 @@ Results of the expectations are logged for later for reporting. Jasmine has several built-in matchers. Here are a few: -`toEqual()` compares objects or primitives and returns true if they are equal - -`toNotEqual()` compares objects or primitives and returns true if they are not equal - -`toMatch()` takes a regex or a string and returns true if it matches - -`toNotMatch()` takes a regex or a string and returns true if it does not match - -`toBeDefined()` returns true if the object or primitive is not `undefined` - -`toBeNull()` returns true if the object or primitive is not `null` - -`toBeTruthy()` returns true if the object or primitive evaluates to true - -`toBeFalsy()` returns true if the object or primitive evaluates to false - -`toContain()` returns true if an array or string contains the passed variable. - -`toNotContain()` returns true if an array or string does not contain the passed variable. +>`expect(x).toEqual(y);` compares objects or primitives `x` and `y` and passes if they are equivalent +> +>`expect(x).toMatch(pattern);` compares `x` to string or regular expression `pattern` and passes if they match +> +>`expect(x).toBeDefined();` passes if `x` is not `undefined` +> +>`expect(x).toBeNull();` passes if `x` is not `null` +> +>`expect(x).toBeTruthy();` passes if `x` evaluates to true +> +>`expect(x).toBeFalsy();` passes if `x` evaluates to false +> +>`expect(x).toContain(y);` passes if array or string `x` contains `y` #### Writing New Matchers -A Matcher has a method name, takes an expected value as it's only parameter, has access to the actual value in this, and then makes a call to this.report with true/false with a failure message. Here's the definition of `toEqual()`: +We've provided a small set of matchers that cover many common situations. However, we recommend that you write custom matchers when you want to assert a more specific sort of expectation. Custom matchers help to document the intent of your specs, and can help to remove code duplication in your specs. - jasmine.Matchers.prototype.toEqual = function (expected) { - return this.report((this.actual === expected), - 'Expected ' + expected + ' but got ' + this.actual + '.'); - }; +It's extremely easy to create new matchers for your app. A matcher function receives the actual value as `this.actual`, and zero or more arguments may be passed in the function call. The function should return `true` if the actual value passes the matcher's requirements, and `false` if it does not. -Feel free to define your own matcher as needed in your code. If you'd like to add Matchers to Jasmine, please write tests. +Here's the definition of `toBeLessThan()`: -### Asynchronous Specs + toBeLessThan: function(expected) { + return this.actual < expected; + }; -You may be thinking, "That's all well and good, but you mentioned something about asynchronous tests." +To add the matcher to your suite, call `this.addMatchers()` from within a `before` or `it` block. Call it with an object mapping matcher name to function: -Well, say you need to make a call that is asynchronous - an AJAX API, or some other JavaScript library. That is, the call returns immediately, yet you want to make expectations 'at some point in the future' after some magic happens in the background. - -Jasmine allows you to do this with `runs()` and `waits()` blocks. - -`runs()` blocks by themselves simply run as if they were called directly. The following snippets of code should provide similar results: - - it('should be a test', function () { - var foo = 0 - foo++; - - expect(foo).toEqual(1); - }); - -and - - it('should be a test', function () { - runs( function () { - var foo = 0 - foo++; - - expect(foo).toEqual(1); + beforeEach(function() { + this.addMatchers({ + toBeVisible: function() { return this.actual.isVisible(); } }); }); -multiple `runs()` blocks in a spec will run serially. For example, - - it('should be a test', function () { - runs( function () { - var foo = 0 - foo++; - - expect(foo).toEqual(1); - }); - runs( function () { - var bar = 0 - bar++; - - expect(bar).toEqual(1); - }); - }); - -`runs()` blocks share functional scope -- `this` properties will be common to all blocks, but declared `var`'s will not! - - it('should be a test', function () { - runs( function () { - this.foo = 0 - this.foo++; - var bar = 0; - bar++; - - expect(this.foo).toEqual(1); - expect(bar).toEqual(1); - }); - runs( function () { - this.foo++; - var bar = 0 - bar++; - - expect(foo).toEqual(2); - expect(bar).toEqual(1); - }); - }); - -`runs()` blocks exist so you can test asynchronous processes. The function `waits()` works with `runs()` to provide a naive -timeout before the next block is run. You supply a time to wait before the next `runs()` function is executed. For example: - - it('should be a test', function () { - runs(function () { - this.foo = 0; - var that = this; - setTimeout(function () { - that.foo++; - }, 250); - }); - - runs(function () { - this.expects(this.foo).toEqual(0); - }); - - waits(500); - - runs(function () { - this.expects(this.foo).toEqual(1); - }); - }); - -What's happening here? - -* The first call to `runs()` sets call for 1/4 of a second in the future that increments `this.foo`. -* The second `runs()` is executed immediately and then verifies that `this.foo` was indeed initialized to zero in the previous `runs()`. -* Then we wait for half a second. -* Then the last call to `runs()` expects that `this.foo` was incremented by the `setTimeout`. - - ### Suites Specs are grouped in Suites. Suites are defined using the global `describe()` function: - describe('One suite', function () { - it('has a test', function () { - ... - }); - - it('has another test', function () { - ... - }); - }); + describe('One suite', function () { + it('has a test', function () { + ... + }); + + it('has another test', function () { + ... + }); + }); The Suite name is so that reporting is more descriptive. Suites are executed in the order in which `describe()` calls are made, usually in the order in which their script files are included. Additionally, specs within a suite share a functional scope. So you may declare variables inside a describe block and they are accessible from within your specs. For example: - describe('A suite with some variables', function () { - var bar = 0 - - it('has a test', function () { - bar++; - expect(bar).toEqual(1); - }); - - it('has another test', function () { - bar++; - expect(bar).toEqual(2); - }); - }); + describe('A suite with some variables', function () { + var bar = 0 + + it('has a test', function () { + bar++; + expect(bar).toEqual(1); + }); + + it('has another test', function () { + bar++; + expect(bar).toEqual(2); + }); + }); #### beforeEach @@ -344,26 +251,26 @@ A runner can also have an afterEach declarations. Runner afterEach functions are Jasmine supports nested describes. An example: describe('some suite', function () { - + var suiteWideFoo; - + beforeEach(function () { suiteWideFoo = 0; }); - + describe('some nested suite', function() { var nestedSuiteBar; beforeEach(function() { nestedSuiteBar=1; - }); - + }); + it('nested expectation', function () { expect(suiteWideFoo).toEqual(0); expect(nestedSuiteBar).toEqual(1); }); - + }); - + it('top-level describe', function () { expect(suiteWideFoo).toEqual(0); expect(nestedSuiteBar).toEqual(undefined); @@ -386,7 +293,7 @@ Here are a few examples: var Klass.prototype.methodWithCallback = function (callback) { return callback('foo'); }; - + ... it('should spy on Klass#method') { @@ -430,23 +337,23 @@ Spies can be very useful for testing AJAX or other asynchronous behaviors that t There are spy-specfic matchers that are very handy. -`wasCalled()` returns true if the object is a spy and was called +`expect(x).wasCalled()` passes if `x` is a spy and was called -`wasCalledWith(arguments)` returns true if the object is a spy and was called with the passed arguments +`expect(x).wasCalledWith(arguments)` passes if `x` is a spy and was called with the specified arguments -`wasNotCalled()` returns true if the object is a spy and was not called +`expect(x).wasNotCalled()` passes if `x` is a spy and was not called -`wasNotCalledWith(arguments)` returns true if the object is a spy and was not called with the passed arguments +`expect(x).wasNotCalledWith(arguments)` passes if `x` is a spy and was not called with the specified arguments Spies can be trained to respond in a variety of ways when invoked: -`andCallThrough()`: spies on AND calls the original function spied on +`spyOn(x, 'method').andCallThrough()`: spies on AND calls the original function spied on -`andReturn(arguments)`: returns passed arguments when spy is called +`spyOn(x, 'method').andReturn(arguments)`: returns passed arguments when spy is called -`andThrow(exception)`: throws passed exception when spy is called +`spyOn(x, 'method').andThrow(exception)`: throws passed exception when spy is called -`andCallFake(function)`: calls passed function when spy is called +`spyOn(x, 'method').andCallFake(function)`: calls passed function when spy is called Spies have some useful properties: @@ -462,18 +369,117 @@ Spies are automatically removed after each spec. They may be set in the beforeEa Specs may be disabled by calling `xit()` instead of `it()`. Suites may be disabled by calling `xdescribe()` instead of `describe()`. A simple find/replace in your editor of choice will allow you to run a subset of your specs. +### Asynchronous Specs + +You may be thinking, "That's all very nice, but what's this about asynchronous tests?" + +Well, say you need to make a call that is asynchronous - an AJAX API, event callback, or some other JavaScript library. That is, the call returns immediately, yet you want to make expectations 'at some point in the future' after some magic happens in the background. + +Jasmine allows you to do this with `runs()` and `waits()` blocks. + +`runs()` blocks by themselves simply run as if they were called directly. The following snippets of code should provide similar results: + + it('should be a test', function () { + var foo = 0 + foo++; + + expect(foo).toEqual(1); + }); + +and + + it('should be a test', function () { + runs( function () { + var foo = 0 + foo++; + + expect(foo).toEqual(1); + }); + }); + +multiple `runs()` blocks in a spec will run serially. For example, + + it('should be a test', function () { + runs( function () { + var foo = 0 + foo++; + + expect(foo).toEqual(1); + }); + runs( function () { + var bar = 0 + bar++; + + expect(bar).toEqual(1); + }); + }); + +`runs()` blocks share functional scope -- `this` properties will be common to all blocks, but declared `var`'s will not! + + it('should be a test', function () { + runs( function () { + this.foo = 0 + this.foo++; + var bar = 0; + bar++; + + expect(this.foo).toEqual(1); + expect(bar).toEqual(1); + }); + runs( function () { + this.foo++; + var bar = 0 + bar++; + + expect(foo).toEqual(2); + expect(bar).toEqual(1); + }); + }); + +`runs()` blocks exist so you can test asynchronous processes. The function `waits()` works with `runs()` to provide a naive +timeout before the next block is run. You supply a time to wait before the next `runs()` function is executed. For example: + + it('should be a test', function () { + runs(function () { + this.foo = 0; + var that = this; + setTimeout(function () { + that.foo++; + }, 250); + }); + + runs(function () { + this.expects(this.foo).toEqual(0); + }); + + waits(500); + + runs(function () { + this.expects(this.foo).toEqual(1); + }); + }); + +What's happening here? + +* The first call to `runs()` sets call for 1/4 of a second in the future that increments `this.foo`. +* The second `runs()` is executed immediately and then verifies that `this.foo` was indeed initialized to zero in the previous `runs()`. +* Then we wait for half a second. +* Then the last call to `runs()` expects that `this.foo` was incremented by the `setTimeout`. + ## Support We now have a Google Group for support & discussion. * Homepage: [http://groups.google.com/group/jasmine-js](http://groups.google.com/group/jasmine-js) * Group email: [jasmine-js@googlegroups.com](jasmine-js@googlegroups.com) +* Current build status of Jasmine is visible at [ci.pivotallabs.com](http://ci.pivotallabs.com) ## Maintainers * [Davis W. Frank](mailto:dwfrank@pivotallabs.com), Pivotal Labs * [Rajan Agaskar](mailto:rajan@pivotallabs.com), Pivotal Labs +* [Christian Williams](mailto:xian@pivotallabs.com), Pivotal Labs ## Acknowledgments * A big shout out to the various JavaScript test framework authors, especially TJ for [JSpec](http://github.com/visionmedia/jspec/tree/master) - we played with it a bit before deciding that we really needed to roll our own. * Thanks to Pivot [Jessica Miller](http://www.jessicamillerworks.com/) for our fancy pass/fail/pending icons -* Huge contributions have been made by [Christian Williams](mailto:xian@pivotallabs.com) (the master "spy" coder), [Erik Hanson](mailto:erik@pivotallabs.com), [Adam Abrons](mailto:adam@pivotallabs.com) and [Carl Jackson](mailto:carl@pivotallabs.com), and many other Pivots. +* Huge contributions have been made by [Erik Hanson](mailto:erik@pivotallabs.com), [Adam Abrons](mailto:adam@pivotallabs.com) and [Carl Jackson](mailto:carl@pivotallabs.com), and many other Pivots. diff --git a/Rakefile b/Rakefile index 4ce8838..7a88614 100644 --- a/Rakefile +++ b/Rakefile @@ -56,10 +56,10 @@ namespace :jasmine do end desc 'Builds lib/jasmine from source' - task :build => :lint do + task :build => [:lint, 'gems:geminstaller'] do puts 'Building Jasmine from source' require 'json' - + sources = jasmine_sources version = version_hash @@ -119,17 +119,37 @@ jasmine.version_= { end namespace :test do - desc "Run continuous integration tests" - task :ci => 'jasmine:build' do - require "spec" - require 'spec/rake/spectask' - Spec::Rake::SpecTask.new(:lambda_ci) do |t| - t.spec_opts = ["--color", "--format", "specdoc"] - t.spec_files = ["spec/jasmine_spec.rb"] - end - Rake::Task[:lambda_ci].invoke - end + desc "Run continuous integration tests using a local Selenium runner" + task :ci => :'ci:local' + namespace :ci do + task :local => 'jasmine:build' do + require "spec" + require 'spec/rake/spectask' + Spec::Rake::SpecTask.new(:lambda_ci) do |t| + t.spec_opts = ["--color", "--format", "specdoc"] + t.spec_files = ["spec/jasmine_spec.rb"] + end + Rake::Task[:lambda_ci].invoke + end + + desc "Run continuous integration tests using Sauce Labs 'Selenium in the Cloud'" + task :saucelabs => ['jasmine:copy_saucelabs_config', 'jasmine:build'] do + ENV['SAUCELABS'] = 'true' + Rake::Task['jasmine:test:ci:local'].invoke + end + end end + desc 'Copy saucelabs.yml to work directory' + task 'copy_saucelabs_config' do + FileUtils.cp '../saucelabs.yml', 'spec' + end +end + +namespace :gems do + desc "Run geminstaller." + task :geminstaller do + `geminstaller --sudo` + end end \ No newline at end of file diff --git a/contrib/ruby/jasmine_runner.rb b/contrib/ruby/jasmine_runner.rb index 5a6b1ef..2388396 100644 --- a/contrib/ruby/jasmine_runner.rb +++ b/contrib/ruby/jasmine_runner.rb @@ -228,46 +228,53 @@ module Jasmine @browser = options[:browser] ? options[:browser].delete(:browser) : 'firefox' @selenium_pid = nil @jasmine_server_pid = nil + @selenium_host = 'localhost' + @jasmine_server_port = Jasmine::find_unused_port + @selenium_server_port = Jasmine::find_unused_port end def start - start_servers - @client = Jasmine::SimpleClient.new("localhost", @selenium_server_port, "*#{@browser}", "http://localhost:#{@jasmine_server_port}/") + start_jasmine_server + start_selenium_server + @client = Jasmine::SimpleClient.new(@selenium_host, @selenium_server_port, "*#{@browser}", "http://localhost:#{@jasmine_server_port}/") @client.connect end def stop @client.disconnect - stop_servers + stop_selenium_server + stop_jasmine_server end - def start_servers - @jasmine_server_port = Jasmine::find_unused_port - @selenium_server_port = Jasmine::find_unused_port - - @selenium_pid = fork do - Process.setpgrp - exec "java -jar #{@selenium_jar_path} -port #{@selenium_server_port} > /dev/null 2>&1" - end - puts "selenium started. pid is #{@selenium_pid}" - + def start_jasmine_server @jasmine_server_pid = fork do Process.setpgrp Jasmine::SimpleServer.start(@jasmine_server_port, @spec_files, @dir_mappings, @options) exit! 0 end puts "jasmine server started. pid is #{@jasmine_server_pid}" - - Jasmine::wait_for_listener(@selenium_server_port, "selenium server") Jasmine::wait_for_listener(@jasmine_server_port, "jasmine server") end - def stop_servers - puts "shutting down the servers..." - Jasmine::kill_process_group(@selenium_pid) if @selenium_pid + def start_selenium_server + @selenium_pid = fork do + Process.setpgrp + exec "java -jar #{@selenium_jar_path} -port #{@selenium_server_port} > /dev/null 2>&1" + end + puts "selenium started. pid is #{@selenium_pid}" + Jasmine::wait_for_listener(@selenium_server_port, "selenium server") + end + + def stop_jasmine_server + puts "shutting down Jasmine server..." Jasmine::kill_process_group(@jasmine_server_pid) if @jasmine_server_pid end + def stop_selenium_server + puts "shutting down Selenium server..." + Jasmine::kill_process_group(@selenium_pid) if @selenium_pid + end + def run begin start @@ -284,6 +291,40 @@ module Jasmine end end + class SauceLabsRunner < Runner + def initialize(spec_files, dir_mappings, options={}) + @spec_files = spec_files + @dir_mappings = dir_mappings + @options = options + + @browser = options[:browser] ? options[:browser].delete(:browser) : 'firefox' + @jasmine_server_pid = nil + @jasmine_server_port = Jasmine::find_unused_port + @saucelabs_config = SeleniumConfig.new(options[:saucelabs_config], options[:saucelabs_config_file], @jasmine_server_port) + end + + def start_selenium_server + @sauce_tunnel = SauceTunnel.new(@saucelabs_config) + end + + def start + start_jasmine_server + start_selenium_server + @client = Jasmine::SimpleClient.new(@saucelabs_config['selenium_server_address'], + 4444, + @saucelabs_config['selenium_browser_key'], + "http://#{@saucelabs_config['application_address']}") + @client.connect + end + + def stop + @client.disconnect + @sauce_tunnel.shutdown + stop_jasmine_server + end + + end + def self.files(f) result = f result = result.call if result.respond_to?(:call) diff --git a/contrib/ruby/jasmine_spec_builder.rb b/contrib/ruby/jasmine_spec_builder.rb index 260c9e2..06ac7f5 100644 --- a/contrib/ruby/jasmine_spec_builder.rb +++ b/contrib/ruby/jasmine_spec_builder.rb @@ -58,7 +58,7 @@ module Jasmine sleep 0.1 end - @suites = eval_js('JSON.stringify(jsApiReporter.suites())') + @suites = eval_js("var result = jsApiReporter.suites(); if (window.Prototype && result && result.toJSON) { result.toJSON()} else { JSON.stringify(result) }") end def results_for(spec_id) @@ -69,7 +69,7 @@ module Jasmine def load_results @spec_results = {} @spec_ids.each_slice(50) do |slice| - @spec_results.merge!(eval_js("JSON.stringify(jsApiReporter.resultsForSpecs(#{JSON.generate(slice)}))")) + @spec_results.merge!(eval_js("var result = jsApiReporter.resultsForSpecs(#{JSON.generate(slice)}); if (window.Prototype && result && result.toJSON) { result.toJSON()} else { JSON.stringify(result) }")) end @spec_results end diff --git a/cruise_config.rb b/cruise_config.rb new file mode 100644 index 0000000..20d2a5c --- /dev/null +++ b/cruise_config.rb @@ -0,0 +1,21 @@ +# Project-specific configuration for CruiseControl.rb +Project.configure do |project| + + # Send email notifications about broken and fixed builds to email1@your.site, email2@your.site (default: send to nobody) + # project.email_notifier.emails = ['email1@your.site', 'email2@your.site'] + + # Set email 'from' field to john@doe.com: + # project.email_notifier.from = 'john@doe.com' + + # Build the project by invoking rake task 'custom' + project.rake_task = 'jasmine:test:ci:saucelabs' + + # Build the project by invoking shell script "build_my_app.sh". Keep in mind that when the script is invoked, + # current working directory is [cruise data]/projects/your_project/work, so if you do not keep build_my_app.sh + # in version control, it should be '../build_my_app.sh' instead + #project.build_command = 'cp ../saucelabs.yml .' + + # Ping Subversion for new revisions every 5 minutes (default: 30 seconds) + # project.scheduler.polling_interval = 5.minutes + +end diff --git a/doc/files.html b/doc/files.html index bce9c40..bf7627d 100644 --- a/doc/files.html +++ b/doc/files.html @@ -454,7 +454,7 @@ ul.inheritsList