Repository: smartystreets/goconvey Branch: master Commit: a50310f1e3e5 Files: 125 Total size: 1.3 MB Directory structure: gitextract_udu2kvxk/ ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── check_third_party.sh ├── convey/ │ ├── assertions.go │ ├── context.go │ ├── convey.goconvey │ ├── discovery.go │ ├── doc.go │ ├── focused_execution_test.go │ ├── gotest/ │ │ ├── doc_test.go │ │ └── utils.go │ ├── init.go │ ├── isolated_execution_test.go │ ├── nilReporter.go │ ├── reporting/ │ │ ├── console.go │ │ ├── doc.go │ │ ├── dot.go │ │ ├── dot_test.go │ │ ├── gotest.go │ │ ├── gotest_test.go │ │ ├── init.go │ │ ├── json.go │ │ ├── printer.go │ │ ├── printer_test.go │ │ ├── problems.go │ │ ├── problems_test.go │ │ ├── reporter.go │ │ ├── reporter_test.go │ │ ├── reporting.goconvey │ │ ├── reports.go │ │ ├── statistics.go │ │ └── story.go │ ├── reporting_hooks_test.go │ ├── stack_trace_test.go │ ├── story_conventions_test.go │ └── update_assertions.sh ├── examples/ │ ├── assertion_examples_test.go │ ├── bowling_game.go │ ├── bowling_game_test.go │ ├── doc.go │ ├── examples.goconvey │ └── simple_example_test.go ├── go.mod ├── go.sum ├── goconvey.go └── web/ ├── client/ │ ├── composer.html │ ├── index.html │ └── resources/ │ ├── css/ │ │ ├── common.css │ │ ├── composer.css │ │ ├── themes/ │ │ │ ├── dark-bigtext.css │ │ │ ├── dark.css │ │ │ └── light.css │ │ └── tipsy.css │ ├── fonts/ │ │ ├── FontAwesome/ │ │ │ ├── README.md │ │ │ ├── css/ │ │ │ │ └── font-awesome.css │ │ │ └── fonts/ │ │ │ └── FontAwesome.otf │ │ ├── Open_Sans/ │ │ │ └── LICENSE.txt │ │ ├── Orbitron/ │ │ │ └── OFL.txt │ │ └── Oswald/ │ │ └── OFL.txt │ └── js/ │ ├── composer.js │ ├── config.js │ ├── convey.js │ ├── goconvey.js │ ├── lib/ │ │ ├── ansispan.js │ │ ├── diff_match_patch.js │ │ ├── jquery-ui.js │ │ ├── jquery-ui.js.url │ │ ├── jquery.js │ │ ├── jquery.js.url │ │ ├── jquery.pretty-text-diff.js │ │ ├── jquery.pretty-text-diff.js.url │ │ ├── jquery.tipsy.js │ │ ├── jquery.tipsy.js.url │ │ ├── markup.js │ │ ├── markup.js.url │ │ ├── moment.js │ │ ├── moment.js.url │ │ ├── taboverride.js │ │ ├── taboverride.js.url │ │ └── update.sh │ └── poller.js └── server/ ├── api/ │ ├── api.goconvey │ ├── server.go │ └── server_test.go ├── contract/ │ ├── contracts.go │ ├── doc_test.go │ └── result.go ├── executor/ │ ├── contract.go │ ├── coordinator.go │ ├── executor.go │ ├── executor.goconvey │ ├── executor_test.go │ ├── tester.go │ └── tester_test.go ├── messaging/ │ ├── doc_test.go │ └── messages.go ├── parser/ │ ├── packageParser.go │ ├── package_parser_test.go │ ├── parser.go │ ├── parser.goconvey │ ├── parser_test.go │ ├── rules.go │ ├── testParser.go │ └── util.go ├── system/ │ ├── shell.go │ ├── shell_integration_test.go │ ├── shell_test.go │ └── system.goconvey └── watch/ ├── functional_core.go ├── functional_core_test.go ├── imperative_shell.go ├── integration.go ├── integration_test.go ├── integration_testing/ │ ├── doc_test.go │ ├── main.go │ └── sub/ │ ├── .gitignore │ ├── stuff.go │ ├── stuff_test.go │ └── sub.goconvey ├── util_test.go └── watch.goconvey ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ web/client/resources/js/lib/* linguist-vendored ================================================ FILE: .gitignore ================================================ .DS_Store Thumbs.db examples/output.json web/client/reports/ /.idea /goconvey ================================================ FILE: .travis.yml ================================================ arch: - amd64 - ppc64le language: go go: - 1.13.x - 1.14.x - 1.15.x - 1.16.x - master install: - go get -t ./... before_script: ./check_third_party.sh script: go test -short -v ./... sudo: false ================================================ FILE: CONTRIBUTING.md ================================================ # Subject: GoConvey maintainers wanted We'd like to open the project up to additional maintainers who want to move the project forward in a meaningful way. We've spent significant time at SmartyStreets building GoConvey and it has perfectly met (and exceeded) all of our initial design specifications. We've used it to great effect. Being so well-matched to our development workflows at SmartyStreets, we haven't had a need to hack on it lately. This had been frustrating to many in the community who have ideas for the project and would like to see new features released (and some old bugs fixed). The release of Go 1.5 and the new vendoring experiment has been a source of confusion and hassle for those who have already upgraded and find that GoConvey needs to be brought up to speed. GoConvey is a popular 2-pronged, open-source github project (1,600+ stargazers, 100+ forks): - A package you import in your test code that allows you to write BDD-style tests. - An executable that runs a local web server which displays auto-updating test results in a web browser. ---- - https://github.com/smartystreets/goconvey - https://github.com/smartystreets/goconvey/wiki _I should mention that the [assertions package](https://github.com/smartystreets/assertions) imported by the convey package is used by other projects at SmartyStreets and so we will be continuing to maintain that project internally._ We hope to hear from you soon. Thanks! --- # Contributing In general, the code posted to the [SmartyStreets github organization](https://github.com/smartystreets) is created to solve specific problems at SmartyStreets that are ancillary to our core products in the address verification industry and may or may not be useful to other organizations or developers. Our reason for posting said code isn't necessarily to solicit feedback or contributions from the community but more as a showcase of some of the approaches to solving problems we have adopted. Having stated that, we do consider issues raised by other githubbers as well as contributions submitted via pull requests. When submitting such a pull request, please follow these guidelines: - _Look before you leap:_ If the changes you plan to make are significant, it's in everyone's best interest for you to discuss them with a SmartyStreets team member prior to opening a pull request. - _License and ownership:_ If modifying the `LICENSE.md` file, limit your changes to fixing typographical mistakes. Do NOT modify the actual terms in the license or the copyright by **SmartyStreets, LLC**. Code submitted to SmartyStreets projects becomes property of SmartyStreets and must be compatible with the associated license. - _Testing:_ If the code you are submitting resides in packages/modules covered by automated tests, be sure to add passing tests that cover your changes and assert expected behavior and state. Submit the additional test cases as part of your change set. - _Style:_ Match your approach to **naming** and **formatting** with the surrounding code. Basically, the code you submit shouldn't stand out. - "Naming" refers to such constructs as variables, methods, functions, classes, structs, interfaces, packages, modules, directories, files, etc... - "Formatting" refers to such constructs as whitespace, horizontal line length, vertical function length, vertical file length, indentation, curly braces, etc... ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) 2022 Smarty Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. NOTE: Various optional and subordinate components carry their own licensing requirements and restrictions. Use of those components is subject to the terms and conditions outlined the respective license of each component. ================================================ FILE: README.md ================================================ # SMARTY DISCLAIMER: Subject to the terms of the associated license agreement, this software is freely available for your use. This software is FREE, AS IN PUPPIES, and is a gift. Enjoy your new responsibility. This means that while we may consider enhancement requests, we may or may not choose to entertain requests at our sole and absolute discretion. GoConvey is awesome Go testing ============================== [![Build Status](https://app.travis-ci.com/smartystreets/goconvey.svg?branch=master)](https://app.travis-ci.com/smartystreets/goconvey) [![GoDoc](https://godoc.org/github.com/smartystreets/goconvey?status.svg)](http://godoc.org/github.com/smartystreets/goconvey) Welcome to GoConvey, a yummy Go testing tool for gophers. Works with `go test`. Use it in the terminal or browser according to your viewing pleasure. GoConvey supports the current versions of Go (see the official Go [release policy](https://golang.org/doc/devel/release#policy)). Currently this means Go 1.16 and Go 1.17 are supported. **Features:** - Directly integrates with `go test` - Fully-automatic web UI (works with native Go tests, too) - Huge suite of regression tests - Shows test coverage - Readable, colorized console output (understandable by any manager, IT or not) - Test code generator - Desktop notifications (optional) - Immediately open problem lines in [Sublime Text](http://www.sublimetext.com) ([some assembly required](https://github.com/asuth/subl-handler)) You can ask questions about how to use GoConvey on [StackOverflow](http://stackoverflow.com/questions/ask?tags=goconvey,go&title=GoConvey%3A%20). Use the tags `go` and `goconvey`. **Menu:** - [Installation](#installation) - [Quick start](#quick-start) - [Documentation](#documentation) - [Screenshots](#screenshots) - [Contributors](#contributors) Installation ------------ $ go install github.com/smartystreets/goconvey [Quick start](https://github.com/smartystreets/goconvey/wiki#get-going-in-25-seconds) ----------- Make a test, for example: ```go package package_name import ( "testing" . "github.com/smartystreets/goconvey/convey" ) func TestSpec(t *testing.T) { // Only pass t into top-level Convey calls Convey("Given some integer with a starting value", t, func() { x := 1 Convey("When the integer is incremented", func() { x++ Convey("The value should be greater by one", func() { So(x, ShouldEqual, 2) }) }) }) } ``` #### [In the browser](https://github.com/smartystreets/goconvey/wiki/Web-UI) Start up the GoConvey web server at your project's path: $ $GOPATH/bin/goconvey Then watch the test results display in your browser at: http://localhost:8080 If the browser doesn't open automatically, please click [http://localhost:8080](http://localhost:8080) to open manually. There you have it. ![](http://d79i1fxsrar4t.cloudfront.net/goconvey.co/gc-1-dark.png) As long as GoConvey is running, test results will automatically update in your browser window. ![](http://d79i1fxsrar4t.cloudfront.net/goconvey.co/gc-5-dark.png) The design is responsive, so you can squish the browser real tight if you need to put it beside your code. The [web UI](https://github.com/smartystreets/goconvey/wiki/Web-UI) supports traditional Go tests, so use it even if you're not using GoConvey tests. #### [In the terminal](https://github.com/smartystreets/goconvey/wiki/Execution) Just do what you do best: $ go test Or if you want the output to include the story: $ go test -v [Documentation](https://github.com/smartystreets/goconvey/wiki) ----------- Check out the - [GoConvey wiki](https://github.com/smartystreets/goconvey/wiki), - [![GoDoc](https://godoc.org/github.com/smartystreets/goconvey?status.png)](http://godoc.org/github.com/smartystreets/goconvey) - and the *_test.go files scattered throughout this project. Contributors ---------------------- GoConvey is brought to you by [SmartyStreets](https://github.com/smartystreets) and [several contributors](https://github.com/smartystreets/goconvey/graphs/contributors) (Thanks!). ================================================ FILE: check_third_party.sh ================================================ #!/usr/bin/env bash cd "$(dirname $(realpath $0))" bash ./web/client/resources/js/lib/update.sh if ! (git diff-files --quiet web/client/resources/js/lib); then echo "Third party libraries don't match their .url files." echo "Re-run ./web/client/resources/js/lib/update.sh" exit 1 fi ================================================ FILE: convey/assertions.go ================================================ package convey // DO NOT EDIT: generated by update_assertions.sh //go:generate ./update_assertions.sh import "github.com/smarty/assertions" // These assertions are forwarded from github.com/smarty/assertions // in order to make convey self-contained. var ( ShouldAlmostEqual = assertions.ShouldAlmostEqual ShouldBeBetween = assertions.ShouldBeBetween ShouldBeBetweenOrEqual = assertions.ShouldBeBetweenOrEqual ShouldBeBlank = assertions.ShouldBeBlank ShouldBeChronological = assertions.ShouldBeChronological ShouldBeEmpty = assertions.ShouldBeEmpty ShouldBeError = assertions.ShouldBeError ShouldBeFalse = assertions.ShouldBeFalse ShouldBeGreaterThan = assertions.ShouldBeGreaterThan ShouldBeGreaterThanOrEqualTo = assertions.ShouldBeGreaterThanOrEqualTo ShouldBeIn = assertions.ShouldBeIn ShouldBeLessThan = assertions.ShouldBeLessThan ShouldBeLessThanOrEqualTo = assertions.ShouldBeLessThanOrEqualTo ShouldBeNil = assertions.ShouldBeNil ShouldBeTrue = assertions.ShouldBeTrue ShouldBeZeroValue = assertions.ShouldBeZeroValue ShouldContain = assertions.ShouldContain ShouldContainKey = assertions.ShouldContainKey ShouldContainSubstring = assertions.ShouldContainSubstring ShouldEndWith = assertions.ShouldEndWith ShouldEqual = assertions.ShouldEqual ShouldEqualJSON = assertions.ShouldEqualJSON ShouldEqualTrimSpace = assertions.ShouldEqualTrimSpace ShouldEqualWithout = assertions.ShouldEqualWithout ShouldHappenAfter = assertions.ShouldHappenAfter ShouldHappenBefore = assertions.ShouldHappenBefore ShouldHappenBetween = assertions.ShouldHappenBetween ShouldHappenOnOrAfter = assertions.ShouldHappenOnOrAfter ShouldHappenOnOrBefore = assertions.ShouldHappenOnOrBefore ShouldHappenOnOrBetween = assertions.ShouldHappenOnOrBetween ShouldHappenWithin = assertions.ShouldHappenWithin ShouldHaveLength = assertions.ShouldHaveLength ShouldHaveSameTypeAs = assertions.ShouldHaveSameTypeAs ShouldImplement = assertions.ShouldImplement ShouldNotAlmostEqual = assertions.ShouldNotAlmostEqual ShouldNotBeBetween = assertions.ShouldNotBeBetween ShouldNotBeBetweenOrEqual = assertions.ShouldNotBeBetweenOrEqual ShouldNotBeBlank = assertions.ShouldNotBeBlank ShouldNotBeChronological = assertions.ShouldNotBeChronological ShouldNotBeEmpty = assertions.ShouldNotBeEmpty ShouldNotBeIn = assertions.ShouldNotBeIn ShouldNotBeNil = assertions.ShouldNotBeNil ShouldNotBeZeroValue = assertions.ShouldNotBeZeroValue ShouldNotContain = assertions.ShouldNotContain ShouldNotContainKey = assertions.ShouldNotContainKey ShouldNotContainSubstring = assertions.ShouldNotContainSubstring ShouldNotEndWith = assertions.ShouldNotEndWith ShouldNotEqual = assertions.ShouldNotEqual ShouldNotHappenOnOrBetween = assertions.ShouldNotHappenOnOrBetween ShouldNotHappenWithin = assertions.ShouldNotHappenWithin ShouldNotHaveSameTypeAs = assertions.ShouldNotHaveSameTypeAs ShouldNotImplement = assertions.ShouldNotImplement ShouldNotPanic = assertions.ShouldNotPanic ShouldNotPanicWith = assertions.ShouldNotPanicWith ShouldNotPointTo = assertions.ShouldNotPointTo ShouldNotResemble = assertions.ShouldNotResemble ShouldNotStartWith = assertions.ShouldNotStartWith ShouldPanic = assertions.ShouldPanic ShouldPanicWith = assertions.ShouldPanicWith ShouldPointTo = assertions.ShouldPointTo ShouldResemble = assertions.ShouldResemble ShouldStartWith = assertions.ShouldStartWith ShouldWrap = assertions.ShouldWrap ) ================================================ FILE: convey/context.go ================================================ package convey import ( "fmt" "github.com/jtolds/gls" "github.com/smartystreets/goconvey/convey/reporting" ) type conveyErr struct { fmt string params []any } func (e *conveyErr) Error() string { return fmt.Sprintf(e.fmt, e.params...) } func conveyPanic(fmt string, params ...any) { panic(&conveyErr{fmt, params}) } const ( missingGoTest = `Top-level calls to Convey(...) need a reference to the *testing.T. Hint: Convey("description here", t, func() { /* notice that the second argument was the *testing.T (t)! */ }) ` extraGoTest = `Only the top-level call to Convey(...) needs a reference to the *testing.T.` noStackContext = "Convey operation made without context on goroutine stack.\n" + "Hint: Perhaps you meant to use `Convey(..., func(c C){...})` ?" differentConveySituations = "Different set of Convey statements on subsequent pass!\nDid not expect %#v." multipleIdenticalConvey = "Multiple convey suites with identical names: %#v" ) const ( failureHalt = "___FAILURE_HALT___" nodeKey = "node" ) ///////////////////////////////// Stack Context ///////////////////////////////// func getCurrentContext() *context { ctx, ok := ctxMgr.GetValue(nodeKey) if ok { return ctx.(*context) } return nil } func mustGetCurrentContext() *context { ctx := getCurrentContext() if ctx == nil { conveyPanic(noStackContext) } return ctx } //////////////////////////////////// Context //////////////////////////////////// // context magically handles all coordination of Convey's and So assertions. // // It is tracked on the stack as goroutine-local-storage with the gls package, // or explicitly if the user decides to call convey like: // // Convey(..., func(c C) { // c.So(...) // }) // // This implements the `C` interface. type context struct { reporter reporting.Reporter children map[string]*context resets []func() executedOnce bool expectChildRun *bool complete bool focus bool failureMode FailureMode stackMode StackMode } // rootConvey is the main entry point to a test suite. This is called when // there's no context in the stack already, and items must contain a `t` object, // or this panics. func rootConvey(items ...any) { entry := discover(items) if entry.Test == nil { conveyPanic(missingGoTest) } expectChildRun := true ctx := &context{ reporter: buildReporter(), children: make(map[string]*context), expectChildRun: &expectChildRun, focus: entry.Focus, failureMode: defaultFailureMode.combine(entry.FailMode), stackMode: defaultStackMode.combine(entry.StackMode), } ctxMgr.SetValues(gls.Values{nodeKey: ctx}, func() { ctx.reporter.BeginStory(reporting.NewStoryReport(entry.Test)) defer ctx.reporter.EndStory() for ctx.shouldVisit() { ctx.conveyInner(entry.Situation, entry.Func) expectChildRun = true } }) } //////////////////////////////////// Methods //////////////////////////////////// func (ctx *context) SkipConvey(items ...any) { ctx.Convey(items, skipConvey) } func (ctx *context) FocusConvey(items ...any) { ctx.Convey(items, focusConvey) } func (ctx *context) Convey(items ...any) { entry := discover(items) // we're a branch, or leaf (on the wind) if entry.Test != nil { conveyPanic(extraGoTest) } if ctx.focus && !entry.Focus { return } var inner_ctx *context if ctx.executedOnce { var ok bool inner_ctx, ok = ctx.children[entry.Situation] if !ok { conveyPanic(differentConveySituations, entry.Situation) } } else { if _, ok := ctx.children[entry.Situation]; ok { conveyPanic(multipleIdenticalConvey, entry.Situation) } inner_ctx = &context{ reporter: ctx.reporter, children: make(map[string]*context), expectChildRun: ctx.expectChildRun, focus: entry.Focus, failureMode: ctx.failureMode.combine(entry.FailMode), stackMode: ctx.stackMode.combine(entry.StackMode), } ctx.children[entry.Situation] = inner_ctx } if inner_ctx.shouldVisit() { ctxMgr.SetValues(gls.Values{nodeKey: inner_ctx}, func() { inner_ctx.conveyInner(entry.Situation, entry.Func) }) } } func (ctx *context) SkipSo(stuff ...any) { ctx.assertionReport(reporting.NewSkipReport()) } func (ctx *context) So(actual any, assert Assertion, expected ...any) { if result := assert(actual, expected...); result == assertionSuccess { ctx.assertionReport(reporting.NewSuccessReport()) } else { ctx.assertionReport(reporting.NewFailureReport(result, ctx.shouldShowStack())) } } func (ctx *context) SoMsg(msg string, actual any, assert Assertion, expected ...any) { if result := assert(actual, expected...); result == assertionSuccess { ctx.assertionReport(reporting.NewSuccessReport()) return } else { ctx.reporter.Enter(reporting.NewScopeReport(msg)) defer ctx.reporter.Exit() ctx.assertionReport(reporting.NewFailureReport(result, ctx.shouldShowStack())) } } func (ctx *context) Reset(action func()) { /* TODO: Failure mode configuration */ ctx.resets = append(ctx.resets, action) } func (ctx *context) Print(items ...any) (int, error) { fmt.Fprint(ctx.reporter, items...) return fmt.Print(items...) } func (ctx *context) Println(items ...any) (int, error) { fmt.Fprintln(ctx.reporter, items...) return fmt.Println(items...) } func (ctx *context) Printf(format string, items ...any) (int, error) { fmt.Fprintf(ctx.reporter, format, items...) return fmt.Printf(format, items...) } //////////////////////////////////// Private //////////////////////////////////// // shouldVisit returns true iff we should traverse down into a Convey. Note // that just because we don't traverse a Convey this time, doesn't mean that // we may not traverse it on a subsequent pass. func (c *context) shouldVisit() bool { return !c.complete && *c.expectChildRun } func (c *context) shouldShowStack() bool { return c.stackMode == StackFail } // conveyInner is the function which actually executes the user's anonymous test // function body. At this point, Convey or RootConvey has decided that this // function should actually run. func (ctx *context) conveyInner(situation string, f func(C)) { // Record/Reset state for next time. defer func() { ctx.executedOnce = true // This is only needed at the leaves, but there's no harm in also setting it // when returning from branch Convey's *ctx.expectChildRun = false }() // Set up+tear down our scope for the reporter ctx.reporter.Enter(reporting.NewScopeReport(situation)) defer ctx.reporter.Exit() // Recover from any panics in f, and assign the `complete` status for this // node of the tree. defer func() { ctx.complete = true if problem := recover(); problem != nil { if problem, ok := problem.(*conveyErr); ok { panic(problem) } if problem != failureHalt { ctx.reporter.Report(reporting.NewErrorReport(problem)) } } else { for _, child := range ctx.children { if !child.complete { ctx.complete = false return } } } }() // Resets are registered as the `f` function executes, so nil them here. // All resets are run in registration order (FIFO). ctx.resets = []func(){} defer func() { for _, r := range ctx.resets { // panics handled by the previous defer r() } }() if f == nil { // if f is nil, this was either a Convey(..., nil), or a SkipConvey ctx.reporter.Report(reporting.NewSkipReport()) } else { f(ctx) } } // assertionReport is a helper for So and SkipSo which makes the report and // then possibly panics, depending on the current context's failureMode. func (ctx *context) assertionReport(r *reporting.AssertionResult) { ctx.reporter.Report(r) if r.Failure != "" && ctx.failureMode == FailureHalts { panic(failureHalt) } } ================================================ FILE: convey/convey.goconvey ================================================ #ignore -timeout=1s #-covermode=count #-coverpkg=github.com/smartystreets/goconvey/convey,github.com/smartystreets/goconvey/convey/gotest,github.com/smartystreets/goconvey/convey/reporting ================================================ FILE: convey/discovery.go ================================================ package convey type actionSpecifier uint8 const ( noSpecifier actionSpecifier = iota skipConvey focusConvey ) type suite struct { Situation string Test t Focus bool Func func(C) // nil means skipped FailMode FailureMode StackMode StackMode } func newSuite(situation string, failureMode FailureMode, stackMode StackMode, f func(C), test t, specifier actionSpecifier) *suite { ret := &suite{ Situation: situation, Test: test, Func: f, FailMode: failureMode, StackMode: stackMode, } switch specifier { case skipConvey: ret.Func = nil case focusConvey: ret.Focus = true } return ret } func discover(items []any) *suite { name, items := parseName(items) test, items := parseGoTest(items) failure, items := parseFailureMode(items) stack, items := parseStackMode(items) action, items := parseAction(items) specifier, items := parseSpecifier(items) if len(items) != 0 { conveyPanic(parseError) } return newSuite(name, failure, stack, action, test, specifier) } func item(items []any) any { if len(items) == 0 { conveyPanic(parseError) } return items[0] } func parseName(items []any) (string, []any) { if name, parsed := item(items).(string); parsed { return name, items[1:] } conveyPanic(parseError) panic("never get here") } func parseGoTest(items []any) (t, []any) { if test, parsed := item(items).(t); parsed { return test, items[1:] } return nil, items } func parseFailureMode(items []any) (FailureMode, []any) { if mode, parsed := item(items).(FailureMode); parsed { return mode, items[1:] } return FailureInherits, items } func parseStackMode(items []any) (StackMode, []any) { if mode, parsed := item(items).(StackMode); parsed { return mode, items[1:] } return StackInherits, items } func parseAction(items []any) (func(C), []any) { switch x := item(items).(type) { case nil: return nil, items[1:] case func(C): return x, items[1:] case func(): return func(C) { x() }, items[1:] } conveyPanic(parseError) panic("never get here") } func parseSpecifier(items []any) (actionSpecifier, []any) { if len(items) == 0 { return noSpecifier, items } if spec, ok := items[0].(actionSpecifier); ok { return spec, items[1:] } conveyPanic(parseError) panic("never get here") } // This interface allows us to pass the *testing.T struct // throughout the internals of this package without ever // having to import the "testing" package. type t interface { Fail() } const parseError = "You must provide a name (string), then a *testing.T (if in outermost scope), an optional FailureMode and / or StackMode, and then an action (func())." ================================================ FILE: convey/doc.go ================================================ // Package convey contains all of the public-facing entry points to this project. // This means that it should never be required of the user to import any other // packages from this project as they serve internal purposes. package convey import "github.com/smartystreets/goconvey/convey/reporting" ////////////////////////////////// suite ////////////////////////////////// // C is the Convey context which you can optionally obtain in your action // by calling Convey like: // // Convey(..., func(c C) { // ... // }) // // See the documentation on Convey for more details. // // All methods in this context behave identically to the global functions of the // same name in this package. type C interface { Convey(items ...any) SkipConvey(items ...any) FocusConvey(items ...any) So(actual any, assert Assertion, expected ...any) SoMsg(msg string, actual any, assert Assertion, expected ...any) SkipSo(stuff ...any) Reset(action func()) Println(items ...any) (int, error) Print(items ...any) (int, error) Printf(format string, items ...any) (int, error) } // Convey is the method intended for use when declaring the scopes of // a specification. Each scope has a description and a func() which may contain // other calls to Convey(), Reset() or Should-style assertions. Convey calls can // be nested as far as you see fit. // // IMPORTANT NOTE: The top-level Convey() within a Test method // must conform to the following signature: // // Convey(description string, t *testing.T, action func()) // // All other calls should look like this (no need to pass in *testing.T): // // Convey(description string, action func()) // // Don't worry, goconvey will panic if you get it wrong so you can fix it. // // Additionally, you may explicitly obtain access to the Convey context by doing: // // Convey(description string, action func(c C)) // // You may need to do this if you want to pass the context through to a // goroutine, or to close over the context in a handler to a library which // calls your handler in a goroutine (httptest comes to mind). // // All Convey()-blocks also accept an optional parameter of FailureMode which sets // how goconvey should treat failures for So()-assertions in the block and // nested blocks. See the constants in this file for the available options. // // By default it will inherit from its parent block and the top-level blocks // default to the FailureHalts setting. // // This parameter is inserted before the block itself: // // Convey(description string, t *testing.T, mode FailureMode, action func()) // Convey(description string, mode FailureMode, action func()) // // See the examples package for, well, examples. func Convey(items ...any) { if ctx := getCurrentContext(); ctx == nil { rootConvey(items...) } else { ctx.Convey(items...) } } // SkipConvey is analogous to Convey except that the scope is not executed // (which means that child scopes defined within this scope are not run either). // The reporter will be notified that this step was skipped. func SkipConvey(items ...any) { Convey(append(items, skipConvey)...) } // FocusConvey is has the inverse effect of SkipConvey. If the top-level // Convey is changed to `FocusConvey`, only nested scopes that are defined // with FocusConvey will be run. The rest will be ignored completely. This // is handy when debugging a large suite that runs a misbehaving function // repeatedly as you can disable all but one of that function // without swaths of `SkipConvey` calls, just a targeted chain of calls // to FocusConvey. func FocusConvey(items ...any) { Convey(append(items, focusConvey)...) } // Reset registers a cleanup function to be run after each Convey() // in the same scope. See the examples package for a simple use case. func Reset(action func()) { mustGetCurrentContext().Reset(action) } /////////////////////////////////// Assertions /////////////////////////////////// // Assertion is an alias for a function with a signature that the convey.So() // method can handle. Any future or custom assertions should conform to this // method signature. The return value should be an empty string if the assertion // passes and a well-formed failure message if not. type Assertion func(actual any, expected ...any) string const assertionSuccess = "" // So is the means by which assertions are made against the system under test. // The majority of exported names in the assertions package begin with the word // 'Should' and describe how the first argument (actual) should compare with any // of the final (expected) arguments. How many final arguments are accepted // depends on the particular assertion that is passed in as the assert argument. // See the examples package for use cases and the assertions package for // documentation on specific assertion methods. A failing assertion will // cause t.Fail() to be invoked--you should never call this method (or other // failure-inducing methods) in your test code. Leave that to GoConvey. func So(actual any, assert Assertion, expected ...any) { mustGetCurrentContext().So(actual, assert, expected...) } // SoMsg is an extension of So that allows you to specify a message to report on error. func SoMsg(msg string, actual any, assert Assertion, expected ...any) { mustGetCurrentContext().SoMsg(msg, actual, assert, expected...) } // SkipSo is analogous to So except that the assertion that would have been passed // to So is not executed and the reporter is notified that the assertion was skipped. func SkipSo(stuff ...any) { mustGetCurrentContext().SkipSo() } // FailureMode is a type which determines how the So() blocks should fail // if their assertion fails. See constants further down for acceptable values type FailureMode string // StackMode is a type which determines whether the So() blocks should report // stack traces their assertion fails. See constants further down for acceptable values type StackMode string const ( // FailureContinues is a failure mode which prevents failing // So()-assertions from halting Convey-block execution, instead // allowing the test to continue past failing So()-assertions. FailureContinues FailureMode = "continue" // FailureHalts is the default setting for a top-level Convey()-block // and will cause all failing So()-assertions to halt further execution // in that test-arm and continue on to the next arm. FailureHalts FailureMode = "halt" // FailureInherits is the default setting for failure-mode, it will // default to the failure-mode of the parent block. You should never // need to specify this mode in your tests.. FailureInherits FailureMode = "inherits" // StackError is a stack mode which tells Convey to print stack traces // only for errors and not for test failures StackError StackMode = "error" // StackFail is a stack mode which tells Convey to print stack traces // for both errors and test failures StackFail StackMode = "fail" // StackInherits is the default setting for stack-mode, it will // default to the stack-mode of the parent block. You should never // need to specify this mode in your tests.. StackInherits StackMode = "inherits" ) func (f FailureMode) combine(other FailureMode) FailureMode { if other == FailureInherits { return f } return other } var defaultFailureMode FailureMode = FailureHalts // SetDefaultFailureMode allows you to specify the default failure mode // for all Convey blocks. It is meant to be used in an init function to // allow the default mode to be changed across all tests for an entire packgae // but it can be used anywhere. func SetDefaultFailureMode(mode FailureMode) { if mode == FailureContinues || mode == FailureHalts { defaultFailureMode = mode } else { panic("You may only use the constants named 'FailureContinues' and 'FailureHalts' as default failure modes.") } } func (s StackMode) combine(other StackMode) StackMode { if other == StackInherits { return s } return other } var defaultStackMode StackMode = StackError // SetDefaultStackMode allows you to specify the default stack mode // for all Convey blocks. It is meant to be used in an init function to // allow the default mode to be changed across all tests for an entire packgae // but it can be used anywhere. func SetDefaultStackMode(mode StackMode) { if mode == StackError || mode == StackFail { defaultStackMode = mode } else { panic("You may only use the constants named 'StackError' and 'StackFail' as default stack modes.") } } //////////////////////////////////// Print functions //////////////////////////////////// // Print is analogous to fmt.Print (and it even calls fmt.Print). It ensures that // output is aligned with the corresponding scopes in the web UI. func Print(items ...any) (written int, err error) { return mustGetCurrentContext().Print(items...) } // Print is analogous to fmt.Println (and it even calls fmt.Println). It ensures that // output is aligned with the corresponding scopes in the web UI. func Println(items ...any) (written int, err error) { return mustGetCurrentContext().Println(items...) } // Print is analogous to fmt.Printf (and it even calls fmt.Printf). It ensures that // output is aligned with the corresponding scopes in the web UI. func Printf(format string, items ...any) (written int, err error) { return mustGetCurrentContext().Printf(format, items...) } /////////////////////////////////////////////////////////////////////////////// // SuppressConsoleStatistics prevents automatic printing of console statistics. // Calling PrintConsoleStatistics explicitly will force printing of statistics. func SuppressConsoleStatistics() { reporting.SuppressConsoleStatistics() } // PrintConsoleStatistics may be called at any time to print assertion statistics. // Generally, the best place to do this would be in a TestMain function, // after all tests have been run. Something like this: // // func TestMain(m *testing.M) { // convey.SuppressConsoleStatistics() // result := m.Run() // convey.PrintConsoleStatistics() // os.Exit(result) // } // func PrintConsoleStatistics() { reporting.PrintConsoleStatistics() } ================================================ FILE: convey/focused_execution_test.go ================================================ package convey import "testing" func TestFocusOnlyAtTopLevel(t *testing.T) { output := prepare() FocusConvey("hi", t, func() { output += "done" }) expectEqual(t, "done", output) } func TestFocus(t *testing.T) { output := prepare() FocusConvey("hi", t, func() { output += "1" Convey("bye", func() { output += "2" }) }) expectEqual(t, "1", output) } func TestNestedFocus(t *testing.T) { output := prepare() FocusConvey("hi", t, func() { output += "1" Convey("This shouldn't run", func() { output += "boink!" }) FocusConvey("This should run", func() { output += "2" FocusConvey("The should run too", func() { output += "3" }) Convey("The should NOT run", func() { output += "blah blah blah!" }) }) }) expectEqual(t, "123", output) } func TestForgotTopLevelFocus(t *testing.T) { output := prepare() Convey("1", t, func() { output += "1" FocusConvey("This will be run because the top-level lacks Focus", func() { output += "2" }) Convey("3", func() { output += "3" }) }) expectEqual(t, "1213", output) } ================================================ FILE: convey/gotest/doc_test.go ================================================ package gotest ================================================ FILE: convey/gotest/utils.go ================================================ // Package gotest contains internal functionality. Although this package // contains one or more exported names it is not intended for public // consumption. See the examples package for how to use this project. package gotest import ( "runtime" "strings" ) func ResolveExternalCaller() (file string, line int, name string) { var caller_id uintptr callers := runtime.Callers(0, callStack) for x := 0; x < callers; x++ { caller_id, file, line, _ = runtime.Caller(x) if strings.HasSuffix(file, "_test.go") || strings.HasSuffix(file, "_tests.go") { name = runtime.FuncForPC(caller_id).Name() return } } file, line, name = "", -1, "" return // panic? } const maxStackDepth = 100 // This had better be enough... var callStack []uintptr = make([]uintptr, maxStackDepth, maxStackDepth) ================================================ FILE: convey/init.go ================================================ package convey import ( "flag" "os" "github.com/jtolds/gls" "github.com/smarty/assertions" "github.com/smartystreets/goconvey/convey/reporting" ) func init() { assertions.GoConveyMode(true) declareFlags() ctxMgr = gls.NewContextManager() } func declareFlags() { flag.BoolVar(&json, "convey-json", false, "When true, emits results in JSON blocks. Default: 'false'") flag.BoolVar(&silent, "convey-silent", false, "When true, all output from GoConvey is suppressed.") flag.BoolVar(&story, "convey-story", false, "When true, emits story output, otherwise emits dot output. When not provided, this flag mirrors the value of the '-test.v' flag") if noStoryFlagProvided() { story = verboseEnabled } // FYI: flag.Parse() is called from the testing package. } func noStoryFlagProvided() bool { return !story && !storyDisabled } func buildReporter() reporting.Reporter { selectReporter := os.Getenv("GOCONVEY_REPORTER") switch { case testReporter != nil: return testReporter case json || selectReporter == "json": return reporting.BuildJsonReporter() case silent || selectReporter == "silent": return reporting.BuildSilentReporter() case selectReporter == "dot": // Story is turned on when verbose is set, so we need to check for dot reporter first. return reporting.BuildDotReporter() case story || selectReporter == "story": return reporting.BuildStoryReporter() default: return reporting.BuildDotReporter() } } var ( ctxMgr *gls.ContextManager // only set by internal tests testReporter reporting.Reporter ) var ( json bool silent bool story bool verboseEnabled = flagFound("-test.v=true") storyDisabled = flagFound("-story=false") ) // flagFound parses the command line args manually for flags defined in other // packages. Like the '-v' flag from the "testing" package, for instance. func flagFound(flagValue string) bool { for _, arg := range os.Args { if arg == flagValue { return true } } return false } ================================================ FILE: convey/isolated_execution_test.go ================================================ package convey import ( "strconv" "testing" "time" ) func TestSingleScope(t *testing.T) { output := prepare() Convey("hi", t, func() { output += "done" }) expectEqual(t, "done", output) } func TestSingleScopeWithMultipleConveys(t *testing.T) { output := prepare() Convey("1", t, func() { output += "1" }) Convey("2", t, func() { output += "2" }) expectEqual(t, "12", output) } func TestNestedScopes(t *testing.T) { output := prepare() Convey("a", t, func() { output += "a " Convey("bb", func() { output += "bb " Convey("ccc", func() { output += "ccc | " }) }) }) expectEqual(t, "a bb ccc | ", output) } func TestNestedScopesWithIsolatedExecution(t *testing.T) { output := prepare() Convey("a", t, func() { output += "a " Convey("aa", func() { output += "aa " Convey("aaa", func() { output += "aaa | " }) Convey("aaa1", func() { output += "aaa1 | " }) }) Convey("ab", func() { output += "ab " Convey("abb", func() { output += "abb | " }) }) }) expectEqual(t, "a aa aaa | a aa aaa1 | a ab abb | ", output) } func TestSingleScopeWithConveyAndNestedReset(t *testing.T) { output := prepare() Convey("1", t, func() { output += "1" Reset(func() { output += "a" }) }) expectEqual(t, "1a", output) } func TestPanicingReset(t *testing.T) { output := prepare() Convey("1", t, func() { output += "1" Reset(func() { panic("nooo") }) Convey("runs since the reset hasn't yet", func() { output += "a" }) Convey("but this doesnt", func() { output += "nope" }) }) expectEqual(t, "1a", output) } func TestSingleScopeWithMultipleRegistrationsAndReset(t *testing.T) { output := prepare() Convey("reset after each nested convey", t, func() { Convey("first output", func() { output += "1" }) Convey("second output", func() { output += "2" }) Reset(func() { output += "a" }) }) expectEqual(t, "1a2a", output) } func TestSingleScopeWithMultipleRegistrationsAndMultipleResets(t *testing.T) { output := prepare() Convey("each reset is run at end of each nested convey", t, func() { Convey("1", func() { output += "1" }) Convey("2", func() { output += "2" }) Reset(func() { output += "a" }) Reset(func() { output += "b" }) }) expectEqual(t, "1ab2ab", output) } func Test_Failure_AtHigherLevelScopePreventsChildScopesFromRunning(t *testing.T) { output := prepare() Convey("This step fails", t, func() { So(1, ShouldEqual, 2) Convey("this should NOT be executed", func() { output += "a" }) }) expectEqual(t, "", output) } func Test_Panic_AtHigherLevelScopePreventsChildScopesFromRunning(t *testing.T) { output := prepare() Convey("This step panics", t, func() { Convey("this happens, because the panic didn't happen yet", func() { output += "1" }) output += "a" Convey("this should NOT be executed", func() { output += "2" }) output += "b" panic("Hi") output += "nope" }) expectEqual(t, "1ab", output) } func Test_Panic_InChildScopeDoes_NOT_PreventExecutionOfSiblingScopes(t *testing.T) { output := prepare() Convey("This is the parent", t, func() { Convey("This step panics", func() { panic("Hi") output += "1" }) Convey("This sibling should execute", func() { output += "2" }) }) expectEqual(t, "2", output) } func Test_Failure_InChildScopeDoes_NOT_PreventExecutionOfSiblingScopes(t *testing.T) { output := prepare() Convey("This is the parent", t, func() { Convey("This step fails", func() { So(1, ShouldEqual, 2) output += "1" }) Convey("This sibling should execute", func() { output += "2" }) }) expectEqual(t, "2", output) } func TestResetsAreAlwaysExecutedAfterScope_Panics(t *testing.T) { output := prepare() Convey("This is the parent", t, func() { Convey("This step panics", func() { panic("Hi") output += "1" }) Convey("This sibling step does not panic", func() { output += "a" Reset(func() { output += "b" }) }) Reset(func() { output += "2" }) }) expectEqual(t, "2ab2", output) } func TestResetsAreAlwaysExecutedAfterScope_Failures(t *testing.T) { output := prepare() Convey("This is the parent", t, func() { Convey("This step fails", func() { So(1, ShouldEqual, 2) output += "1" }) Convey("This sibling step does not fail", func() { output += "a" Reset(func() { output += "b" }) }) Reset(func() { output += "2" }) }) expectEqual(t, "2ab2", output) } func TestSkipTopLevel(t *testing.T) { output := prepare() SkipConvey("hi", t, func() { output += "This shouldn't be executed!" }) expectEqual(t, "", output) } func TestSkipNestedLevel(t *testing.T) { output := prepare() Convey("hi", t, func() { output += "yes" SkipConvey("bye", func() { output += "no" }) }) expectEqual(t, "yes", output) } func TestSkipNestedLevelSkipsAllChildLevels(t *testing.T) { output := prepare() Convey("hi", t, func() { output += "yes" SkipConvey("bye", func() { output += "no" Convey("byebye", func() { output += "no-no" }) }) }) expectEqual(t, "yes", output) } func TestIterativeConveys(t *testing.T) { output := prepare() Convey("Test", t, func() { for x := 0; x < 10; x++ { y := strconv.Itoa(x) Convey(y, func() { output += y }) } }) expectEqual(t, "0123456789", output) } func TestClosureVariables(t *testing.T) { output := prepare() i := 0 Convey("A", t, func() { i = i + 1 j := i output += "A" + strconv.Itoa(i) + " " Convey("B", func() { k := j j = j + 1 output += "B" + strconv.Itoa(k) + " " Convey("C", func() { output += "C" + strconv.Itoa(k) + strconv.Itoa(j) + " " }) Convey("D", func() { output += "D" + strconv.Itoa(k) + strconv.Itoa(j) + " " }) }) Convey("C", func() { output += "C" + strconv.Itoa(j) + " " }) }) output += "D" + strconv.Itoa(i) + " " expectEqual(t, "A1 B1 C12 A2 B2 D23 A3 C3 D3 ", output) } func TestClosureVariablesWithReset(t *testing.T) { output := prepare() i := 0 Convey("A", t, func() { i = i + 1 j := i output += "A" + strconv.Itoa(i) + " " Reset(func() { output += "R" + strconv.Itoa(i) + strconv.Itoa(j) + " " }) Convey("B", func() { output += "B" + strconv.Itoa(j) + " " }) Convey("C", func() { output += "C" + strconv.Itoa(j) + " " }) }) output += "D" + strconv.Itoa(i) + " " expectEqual(t, "A1 B1 R11 A2 C2 R22 D2 ", output) } func TestWrappedSimple(t *testing.T) { prepare() output := resetTestString{""} Convey("A", t, func() { func() { output.output += "A " Convey("B", func() { output.output += "B " Convey("C", func() { output.output += "C " }) }) Convey("D", func() { output.output += "D " }) }() }) expectEqual(t, "A B C A D ", output.output) } type resetTestString struct { output string } func addReset(o *resetTestString, f func()) func() { return func() { Reset(func() { o.output += "R " }) f() } } func TestWrappedReset(t *testing.T) { prepare() output := resetTestString{""} Convey("A", t, addReset(&output, func() { output.output += "A " Convey("B", func() { output.output += "B " }) Convey("C", func() { output.output += "C " }) })) expectEqual(t, "A B R A C R ", output.output) } func TestWrappedReset2(t *testing.T) { prepare() output := resetTestString{""} Convey("A", t, func() { Reset(func() { output.output += "R " }) func() { output.output += "A " Convey("B", func() { output.output += "B " Convey("C", func() { output.output += "C " }) }) Convey("D", func() { output.output += "D " }) }() }) expectEqual(t, "A B C R A D R ", output.output) } func TestInfiniteLoopWithTrailingFail(t *testing.T) { done := make(chan int) go func() { Convey("This fails", t, func() { Convey("and this is run", func() { So(true, ShouldEqual, true) }) /* And this prevents the whole block to be marked as run */ So(false, ShouldEqual, true) }) done <- 1 }() select { case <-done: return case <-time.After(1 * time.Millisecond): t.Fail() } } func TestOutermostResetInvokedForGrandchildren(t *testing.T) { output := prepare() Convey("A", t, func() { output += "A " Reset(func() { output += "rA " }) Convey("B", func() { output += "B " Reset(func() { output += "rB " }) Convey("C", func() { output += "C " Reset(func() { output += "rC " }) }) Convey("D", func() { output += "D " Reset(func() { output += "rD " }) }) }) }) expectEqual(t, "A B C rC rB rA A B D rD rB rA ", output) } func TestFailureOption(t *testing.T) { output := prepare() Convey("A", t, FailureHalts, func() { output += "A " So(true, ShouldEqual, true) output += "B " So(false, ShouldEqual, true) output += "C " }) expectEqual(t, "A B ", output) } func TestFailureOption2(t *testing.T) { output := prepare() Convey("A", t, func() { output += "A " So(true, ShouldEqual, true) output += "B " So(false, ShouldEqual, true) output += "C " }) expectEqual(t, "A B ", output) } func TestFailureOption3(t *testing.T) { output := prepare() Convey("A", t, FailureContinues, func() { output += "A " So(true, ShouldEqual, true) output += "B " So(false, ShouldEqual, true) output += "C " }) expectEqual(t, "A B C ", output) } func TestFailureOptionInherit(t *testing.T) { output := prepare() Convey("A", t, FailureContinues, func() { output += "A1 " So(false, ShouldEqual, true) output += "A2 " Convey("B", func() { output += "B1 " So(true, ShouldEqual, true) output += "B2 " So(false, ShouldEqual, true) output += "B3 " }) }) expectEqual(t, "A1 A2 B1 B2 B3 ", output) } func TestFailureOptionInherit2(t *testing.T) { output := prepare() Convey("A", t, FailureHalts, func() { output += "A1 " So(false, ShouldEqual, true) output += "A2 " Convey("B", func() { output += "A1 " So(true, ShouldEqual, true) output += "A2 " So(false, ShouldEqual, true) output += "A3 " }) }) expectEqual(t, "A1 ", output) } func TestFailureOptionInherit3(t *testing.T) { output := prepare() Convey("A", t, FailureHalts, func() { output += "A1 " So(true, ShouldEqual, true) output += "A2 " Convey("B", func() { output += "B1 " So(true, ShouldEqual, true) output += "B2 " So(false, ShouldEqual, true) output += "B3 " }) }) expectEqual(t, "A1 A2 B1 B2 ", output) } func TestFailureOptionNestedOverride(t *testing.T) { output := prepare() Convey("A", t, FailureContinues, func() { output += "A " So(false, ShouldEqual, true) output += "B " Convey("C", FailureHalts, func() { output += "C " So(true, ShouldEqual, true) output += "D " So(false, ShouldEqual, true) output += "E " }) }) expectEqual(t, "A B C D ", output) } func TestFailureOptionNestedOverride2(t *testing.T) { output := prepare() Convey("A", t, FailureHalts, func() { output += "A " So(true, ShouldEqual, true) output += "B " Convey("C", FailureContinues, func() { output += "C " So(true, ShouldEqual, true) output += "D " So(false, ShouldEqual, true) output += "E " }) }) expectEqual(t, "A B C D E ", output) } func TestMultipleInvocationInheritance(t *testing.T) { output := prepare() Convey("A", t, FailureHalts, func() { output += "A1 " So(true, ShouldEqual, true) output += "A2 " Convey("B", FailureContinues, func() { output += "B1 " So(true, ShouldEqual, true) output += "B2 " So(false, ShouldEqual, true) output += "B3 " }) Convey("C", func() { output += "C1 " So(true, ShouldEqual, true) output += "C2 " So(false, ShouldEqual, true) output += "C3 " }) }) expectEqual(t, "A1 A2 B1 B2 B3 A1 A2 C1 C2 ", output) } func TestMultipleInvocationInheritance2(t *testing.T) { output := prepare() Convey("A", t, FailureContinues, func() { output += "A1 " So(true, ShouldEqual, true) output += "A2 " So(false, ShouldEqual, true) output += "A3 " Convey("B", FailureHalts, func() { output += "B1 " So(true, ShouldEqual, true) output += "B2 " So(false, ShouldEqual, true) output += "B3 " }) Convey("C", func() { output += "C1 " So(true, ShouldEqual, true) output += "C2 " So(false, ShouldEqual, true) output += "C3 " }) }) expectEqual(t, "A1 A2 A3 B1 B2 A1 A2 A3 C1 C2 C3 ", output) } func TestSetDefaultFailureMode(t *testing.T) { output := prepare() SetDefaultFailureMode(FailureContinues) // the default is normally FailureHalts defer SetDefaultFailureMode(FailureHalts) Convey("A", t, func() { output += "A1 " So(true, ShouldBeFalse) output += "A2 " }) expectEqual(t, "A1 A2 ", output) } func prepare() string { testReporter = newNilReporter() return "" } ================================================ FILE: convey/nilReporter.go ================================================ package convey import ( "github.com/smartystreets/goconvey/convey/reporting" ) type nilReporter struct{} func (self *nilReporter) BeginStory(story *reporting.StoryReport) {} func (self *nilReporter) Enter(scope *reporting.ScopeReport) {} func (self *nilReporter) Report(report *reporting.AssertionResult) {} func (self *nilReporter) Exit() {} func (self *nilReporter) EndStory() {} func (self *nilReporter) Write(p []byte) (int, error) { return len(p), nil } func newNilReporter() *nilReporter { return &nilReporter{} } ================================================ FILE: convey/reporting/console.go ================================================ package reporting import ( "fmt" "io" ) type console struct{} func (self *console) Write(p []byte) (n int, err error) { return fmt.Print(string(p)) } func NewConsole() io.Writer { return new(console) } ================================================ FILE: convey/reporting/doc.go ================================================ // Package reporting contains internal functionality related // to console reporting and output. Although this package has // exported names is not intended for public consumption. See the // examples package for how to use this project. package reporting ================================================ FILE: convey/reporting/dot.go ================================================ package reporting import "fmt" type dot struct{ out *Printer } func (self *dot) BeginStory(story *StoryReport) {} func (self *dot) Enter(scope *ScopeReport) {} func (self *dot) Report(report *AssertionResult) { if report.Error != nil { fmt.Print(redColor) self.out.Insert(dotError) } else if report.Failure != "" { fmt.Print(yellowColor) self.out.Insert(dotFailure) } else if report.Skipped { fmt.Print(yellowColor) self.out.Insert(dotSkip) } else { fmt.Print(greenColor) self.out.Insert(dotSuccess) } fmt.Print(resetColor) } func (self *dot) Exit() {} func (self *dot) EndStory() {} func (self *dot) Write(content []byte) (written int, err error) { return len(content), nil // no-op } func NewDotReporter(out *Printer) *dot { self := new(dot) self.out = out return self } ================================================ FILE: convey/reporting/dot_test.go ================================================ package reporting import ( "errors" "testing" ) func TestDotReporterAssertionPrinting(t *testing.T) { monochrome() file := newMemoryFile() printer := NewPrinter(file) reporter := NewDotReporter(printer) reporter.Report(NewSuccessReport()) reporter.Report(NewFailureReport("failed", false)) reporter.Report(NewErrorReport(errors.New("error"))) reporter.Report(NewSkipReport()) expected := dotSuccess + dotFailure + dotError + dotSkip if file.buffer != expected { t.Errorf("\nExpected: '%s'\nActual: '%s'", expected, file.buffer) } } func TestDotReporterOnlyReportsAssertions(t *testing.T) { monochrome() file := newMemoryFile() printer := NewPrinter(file) reporter := NewDotReporter(printer) reporter.BeginStory(nil) reporter.Enter(nil) reporter.Exit() reporter.EndStory() if file.buffer != "" { t.Errorf("\nExpected: '(blank)'\nActual: '%s'", file.buffer) } } ================================================ FILE: convey/reporting/gotest.go ================================================ package reporting type gotestReporter struct{ test T } func (self *gotestReporter) BeginStory(story *StoryReport) { self.test = story.Test } func (self *gotestReporter) Enter(scope *ScopeReport) {} func (self *gotestReporter) Report(r *AssertionResult) { if !passed(r) { self.test.Fail() } } func (self *gotestReporter) Exit() {} func (self *gotestReporter) EndStory() { self.test = nil } func (self *gotestReporter) Write(content []byte) (written int, err error) { return len(content), nil // no-op } func NewGoTestReporter() *gotestReporter { return new(gotestReporter) } func passed(r *AssertionResult) bool { return r.Error == nil && r.Failure == "" } ================================================ FILE: convey/reporting/gotest_test.go ================================================ package reporting import "testing" func TestReporterReceivesSuccessfulReport(t *testing.T) { reporter := NewGoTestReporter() test := new(fakeTest) reporter.BeginStory(NewStoryReport(test)) reporter.Report(NewSuccessReport()) if test.failed { t.Errorf("Should have have marked test as failed--the report reflected success.") } } func TestReporterReceivesFailureReport(t *testing.T) { reporter := NewGoTestReporter() test := new(fakeTest) reporter.BeginStory(NewStoryReport(test)) reporter.Report(NewFailureReport("This is a failure.", false)) if !test.failed { t.Errorf("Test should have been marked as failed (but it wasn't).") } } func TestReporterReceivesErrorReport(t *testing.T) { reporter := NewGoTestReporter() test := new(fakeTest) reporter.BeginStory(NewStoryReport(test)) reporter.Report(NewErrorReport("This is an error.")) if !test.failed { t.Errorf("Test should have been marked as failed (but it wasn't).") } } func TestReporterIsResetAtTheEndOfTheStory(t *testing.T) { defer catch(t) reporter := NewGoTestReporter() test := new(fakeTest) reporter.BeginStory(NewStoryReport(test)) reporter.EndStory() reporter.Report(NewSuccessReport()) } func TestReporterNoopMethods(t *testing.T) { reporter := NewGoTestReporter() reporter.Enter(NewScopeReport("title")) reporter.Exit() } func catch(t *testing.T) { if r := recover(); r != nil { t.Log("Getting to this point means we've passed (because we caught a panic appropriately).") } } type fakeTest struct { failed bool } func (self *fakeTest) Fail() { self.failed = true } ================================================ FILE: convey/reporting/init.go ================================================ package reporting import ( "os" "runtime" "strings" ) func init() { if !isColorableTerminal() { monochrome() } if runtime.GOOS == "windows" { success, failure, error_ = dotSuccess, dotFailure, dotError } } func BuildJsonReporter() Reporter { out := NewPrinter(NewConsole()) return NewReporters( NewGoTestReporter(), NewJsonReporter(out)) } func BuildDotReporter() Reporter { out := NewPrinter(NewConsole()) return NewReporters( NewGoTestReporter(), NewDotReporter(out), NewProblemReporter(out), consoleStatistics) } func BuildStoryReporter() Reporter { out := NewPrinter(NewConsole()) return NewReporters( NewGoTestReporter(), NewStoryReporter(out), NewProblemReporter(out), consoleStatistics) } func BuildSilentReporter() Reporter { out := NewPrinter(NewConsole()) return NewReporters( NewGoTestReporter(), NewSilentProblemReporter(out)) } var ( newline = "\n" success = "✔" failure = "✘" error_ = "🔥" skip = "⚠" dotSuccess = "." dotFailure = "x" dotError = "E" dotSkip = "S" errorTemplate = "* %s \nLine %d: - %v \n%s\n" failureTemplate = "* %s \nLine %d:\n%s\n" stackTemplate = "%s\n" ) var ( greenColor = "\033[32m" yellowColor = "\033[33m" redColor = "\033[31m" resetColor = "\033[0m" ) var consoleStatistics = NewStatisticsReporter(NewPrinter(NewConsole())) func SuppressConsoleStatistics() { consoleStatistics.Suppress() } func PrintConsoleStatistics() { consoleStatistics.PrintSummary() } // QuietMode disables all console output symbols. This is only meant to be used // for tests that are internal to goconvey where the output is distracting or // otherwise not needed in the test output. func QuietMode() { success, failure, error_, skip, dotSuccess, dotFailure, dotError, dotSkip = "", "", "", "", "", "", "", "" } func monochrome() { greenColor, yellowColor, redColor, resetColor = "", "", "", "" } func isColorableTerminal() bool { return strings.Contains(os.Getenv("TERM"), "color") } // This interface allows us to pass the *testing.T struct // throughout the internals of this tool without ever // having to import the "testing" package. type T interface { Fail() } ================================================ FILE: convey/reporting/json.go ================================================ // TODO: under unit test package reporting import ( "bytes" "encoding/json" "fmt" "strings" ) type JsonReporter struct { out *Printer currentKey []string current *ScopeResult index map[string]*ScopeResult scopes []*ScopeResult } func (self *JsonReporter) depth() int { return len(self.currentKey) } func (self *JsonReporter) BeginStory(story *StoryReport) {} func (self *JsonReporter) Enter(scope *ScopeReport) { self.currentKey = append(self.currentKey, scope.Title) ID := strings.Join(self.currentKey, "|") if _, found := self.index[ID]; !found { next := newScopeResult(scope.Title, self.depth(), scope.File, scope.Line) self.scopes = append(self.scopes, next) self.index[ID] = next } self.current = self.index[ID] } func (self *JsonReporter) Report(report *AssertionResult) { self.current.Assertions = append(self.current.Assertions, report) } func (self *JsonReporter) Exit() { self.currentKey = self.currentKey[:len(self.currentKey)-1] } func (self *JsonReporter) EndStory() { self.report() self.reset() } func (self *JsonReporter) report() { scopes := []string{} for _, scope := range self.scopes { serialized, err := json.Marshal(scope) if err != nil { self.out.Println(jsonMarshalFailure) panic(err) } var buffer bytes.Buffer json.Indent(&buffer, serialized, "", " ") scopes = append(scopes, buffer.String()) } self.out.Print(fmt.Sprintf("%s\n%s,\n%s\n", OpenJson, strings.Join(scopes, ","), CloseJson)) } func (self *JsonReporter) reset() { self.scopes = []*ScopeResult{} self.index = map[string]*ScopeResult{} self.currentKey = nil } func (self *JsonReporter) Write(content []byte) (written int, err error) { self.current.Output += string(content) return len(content), nil } func NewJsonReporter(out *Printer) *JsonReporter { self := new(JsonReporter) self.out = out self.reset() return self } const OpenJson = ">->->OPEN-JSON->->->" // "⌦" const CloseJson = "<-<-<-CLOSE-JSON<-<-<" // "⌫" const jsonMarshalFailure = ` GOCONVEY_JSON_MARSHALL_FAILURE: There was an error when attempting to convert test results to JSON. Please file a bug report and reference the code that caused this failure if possible. Here's the panic: ` ================================================ FILE: convey/reporting/printer.go ================================================ package reporting import ( "fmt" "io" "strings" ) type Printer struct { out io.Writer prefix string } func (self *Printer) Println(message string, values ...any) { formatted := self.format(message, values...) + newline self.out.Write([]byte(formatted)) } func (self *Printer) Print(message string, values ...any) { formatted := self.format(message, values...) self.out.Write([]byte(formatted)) } func (self *Printer) Insert(text string) { self.out.Write([]byte(text)) } func (self *Printer) format(message string, values ...any) string { var formatted string if len(values) == 0 { formatted = self.prefix + message } else { formatted = self.prefix + fmt_Sprintf(message, values...) } indented := strings.Replace(formatted, newline, newline+self.prefix, -1) return strings.TrimRight(indented, space) } // Extracting fmt.Sprintf to a separate variable circumvents go vet, which, as of go 1.10 is run with go test. var fmt_Sprintf = fmt.Sprintf func (self *Printer) Indent() { self.prefix += pad } func (self *Printer) Dedent() { if len(self.prefix) >= padLength { self.prefix = self.prefix[:len(self.prefix)-padLength] } } func NewPrinter(out io.Writer) *Printer { self := new(Printer) self.out = out return self } const space = " " const pad = space + space const padLength = len(pad) ================================================ FILE: convey/reporting/printer_test.go ================================================ package reporting import "testing" func TestPrint(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) const expected = "Hello, World!" printer.Print(expected) if file.buffer != expected { t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer) } } func TestPrintFormat(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) template := "Hi, %s" name := "Ralph" expected := "Hi, Ralph" printer.Print(template, name) if file.buffer != expected { t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer) } } func TestPrintPreservesEncodedStrings(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) const expected = "= -> %%3D" printer.Print(expected) if file.buffer != expected { t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer) } } func TestPrintln(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) const expected = "Hello, World!" printer.Println(expected) if file.buffer != expected+"\n" { t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer) } } func TestPrintlnFormat(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) template := "Hi, %s" name := "Ralph" expected := "Hi, Ralph\n" printer.Println(template, name) if file.buffer != expected { t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer) } } func TestPrintlnPreservesEncodedStrings(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) const expected = "= -> %%3D" printer.Println(expected) if file.buffer != expected+"\n" { t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer) } } func TestPrintIndented(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) const message = "Hello, World!\nGoodbye, World!" const expected = " Hello, World!\n Goodbye, World!" printer.Indent() printer.Print(message) if file.buffer != expected { t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer) } } func TestPrintDedented(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) const expected = "Hello, World!\nGoodbye, World!" printer.Indent() printer.Dedent() printer.Print(expected) if file.buffer != expected { t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer) } } func TestPrintlnIndented(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) const message = "Hello, World!\nGoodbye, World!" const expected = " Hello, World!\n Goodbye, World!\n" printer.Indent() printer.Println(message) if file.buffer != expected { t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer) } } func TestPrintlnDedented(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) const expected = "Hello, World!\nGoodbye, World!" printer.Indent() printer.Dedent() printer.Println(expected) if file.buffer != expected+"\n" { t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer) } } func TestDedentTooFarShouldNotPanic(t *testing.T) { defer func() { if r := recover(); r != nil { t.Error("Should not have panicked!") } }() file := newMemoryFile() printer := NewPrinter(file) printer.Dedent() t.Log("Getting to this point without panicking means we passed.") } func TestInsert(t *testing.T) { file := newMemoryFile() printer := NewPrinter(file) printer.Indent() printer.Print("Hi") printer.Insert(" there") printer.Dedent() expected := " Hi there" if file.buffer != expected { t.Errorf("Should have written '%s' but instead wrote '%s'.", expected, file.buffer) } } ////////////////// memoryFile //////////////////// type memoryFile struct { buffer string } func (self *memoryFile) Write(p []byte) (n int, err error) { self.buffer += string(p) return len(p), nil } func (self *memoryFile) String() string { return self.buffer } func newMemoryFile() *memoryFile { return new(memoryFile) } ================================================ FILE: convey/reporting/problems.go ================================================ package reporting import "fmt" type problem struct { silent bool out *Printer errors []*AssertionResult failures []*AssertionResult } func (self *problem) BeginStory(story *StoryReport) {} func (self *problem) Enter(scope *ScopeReport) {} func (self *problem) Report(report *AssertionResult) { if report.Error != nil { self.errors = append(self.errors, report) } else if report.Failure != "" { self.failures = append(self.failures, report) } } func (self *problem) Exit() {} func (self *problem) EndStory() { self.show(self.showErrors, redColor) self.show(self.showFailures, yellowColor) self.prepareForNextStory() } func (self *problem) show(display func(), color string) { if !self.silent { fmt.Print(color) } display() if !self.silent { fmt.Print(resetColor) } self.out.Dedent() } func (self *problem) showErrors() { for i, e := range self.errors { if i == 0 { self.out.Println("\nErrors:\n") self.out.Indent() } self.out.Println(errorTemplate, e.File, e.Line, e.Error, e.StackTrace) } } func (self *problem) showFailures() { for i, f := range self.failures { if i == 0 { self.out.Println("\nFailures:\n") self.out.Indent() } self.out.Println(failureTemplate, f.File, f.Line, f.Failure) if f.StackTrace != "" { self.out.Println(stackTemplate, f.StackTrace) } } } func (self *problem) Write(content []byte) (written int, err error) { return len(content), nil // no-op } func NewProblemReporter(out *Printer) *problem { self := new(problem) self.out = out self.prepareForNextStory() return self } func NewSilentProblemReporter(out *Printer) *problem { self := NewProblemReporter(out) self.silent = true return self } func (self *problem) prepareForNextStory() { self.errors = []*AssertionResult{} self.failures = []*AssertionResult{} } ================================================ FILE: convey/reporting/problems_test.go ================================================ package reporting import ( "strings" "testing" ) func TestNoopProblemReporterActions(t *testing.T) { file, reporter := setup() reporter.BeginStory(nil) reporter.Enter(nil) reporter.Exit() expected := "" actual := file.String() if expected != actual { t.Errorf("Expected: '(blank)'\nActual: '%s'", actual) } } func TestReporterPrintsFailuresAndErrorsAtTheEndOfTheStory(t *testing.T) { file, reporter := setup() reporter.Report(NewFailureReport("failed", false)) reporter.Report(NewErrorReport("error")) reporter.Report(NewSuccessReport()) reporter.EndStory() result := file.String() if !strings.Contains(result, "Errors:\n") { t.Errorf("Expected errors, found none.") } if !strings.Contains(result, "Failures:\n") { t.Errorf("Expected failures, found none.") } // Each stack trace looks like: `* /path/to/file.go`, so look for `* `. // With go 1.4+ there is a line in some stack traces that looks like this: // `testing.(*M).Run(0x2082d60a0, 0x25b7c0)` // So we can't just look for "*" anymore. problemCount := strings.Count(result, "* ") if problemCount != 2 { t.Errorf("Expected one failure and one error (total of 2 '*' characters). Got %d", problemCount) } } func setup() (file *memoryFile, reporter *problem) { monochrome() file = newMemoryFile() printer := NewPrinter(file) reporter = NewProblemReporter(printer) return } ================================================ FILE: convey/reporting/reporter.go ================================================ package reporting import "io" type Reporter interface { BeginStory(story *StoryReport) Enter(scope *ScopeReport) Report(r *AssertionResult) Exit() EndStory() io.Writer } type reporters struct{ collection []Reporter } func (self *reporters) BeginStory(s *StoryReport) { self.foreach(func(r Reporter) { r.BeginStory(s) }) } func (self *reporters) Enter(s *ScopeReport) { self.foreach(func(r Reporter) { r.Enter(s) }) } func (self *reporters) Report(a *AssertionResult) { self.foreach(func(r Reporter) { r.Report(a) }) } func (self *reporters) Exit() { self.foreach(func(r Reporter) { r.Exit() }) } func (self *reporters) EndStory() { self.foreach(func(r Reporter) { r.EndStory() }) } func (self *reporters) Write(contents []byte) (written int, err error) { self.foreach(func(r Reporter) { written, err = r.Write(contents) }) return written, err } func (self *reporters) foreach(action func(Reporter)) { for _, r := range self.collection { action(r) } } func NewReporters(collection ...Reporter) *reporters { self := new(reporters) self.collection = collection return self } ================================================ FILE: convey/reporting/reporter_test.go ================================================ package reporting import ( "runtime" "testing" ) func TestEachNestedReporterReceivesTheCallFromTheContainingReporter(t *testing.T) { fake1 := newFakeReporter() fake2 := newFakeReporter() reporter := NewReporters(fake1, fake2) reporter.BeginStory(nil) assertTrue(t, fake1.begun) assertTrue(t, fake2.begun) reporter.Enter(NewScopeReport("scope")) assertTrue(t, fake1.entered) assertTrue(t, fake2.entered) reporter.Report(NewSuccessReport()) assertTrue(t, fake1.reported) assertTrue(t, fake2.reported) reporter.Exit() assertTrue(t, fake1.exited) assertTrue(t, fake2.exited) reporter.EndStory() assertTrue(t, fake1.ended) assertTrue(t, fake2.ended) content := []byte("hi") written, err := reporter.Write(content) assertTrue(t, fake1.written) assertTrue(t, fake2.written) assertEqual(t, written, len(content)) assertNil(t, err) } func assertTrue(t *testing.T, value bool) { if !value { _, _, line, _ := runtime.Caller(1) t.Errorf("Value should have been true (but was false). See line %d", line) } } func assertEqual(t *testing.T, expected, actual int) { if actual != expected { _, _, line, _ := runtime.Caller(1) t.Errorf("Value should have been %d (but was %d). See line %d", expected, actual, line) } } func assertNil(t *testing.T, err error) { if err != nil { _, _, line, _ := runtime.Caller(1) t.Errorf("Error should have been (but wasn't). See line %d. %v", err, line) } } type fakeReporter struct { begun bool entered bool reported bool exited bool ended bool written bool } func newFakeReporter() *fakeReporter { return &fakeReporter{} } func (self *fakeReporter) BeginStory(story *StoryReport) { self.begun = true } func (self *fakeReporter) Enter(scope *ScopeReport) { self.entered = true } func (self *fakeReporter) Report(report *AssertionResult) { self.reported = true } func (self *fakeReporter) Exit() { self.exited = true } func (self *fakeReporter) EndStory() { self.ended = true } func (self *fakeReporter) Write(content []byte) (int, error) { self.written = true return len(content), nil } ================================================ FILE: convey/reporting/reporting.goconvey ================================================ #ignore -timeout=1s ================================================ FILE: convey/reporting/reports.go ================================================ package reporting import ( "encoding/json" "fmt" "runtime" "strings" "github.com/smartystreets/goconvey/convey/gotest" ) ////////////////// ScopeReport //////////////////// type ScopeReport struct { Title string File string Line int } func NewScopeReport(title string) *ScopeReport { file, line, _ := gotest.ResolveExternalCaller() self := new(ScopeReport) self.Title = title self.File = file self.Line = line return self } ////////////////// ScopeResult //////////////////// type ScopeResult struct { Title string File string Line int Depth int Assertions []*AssertionResult Output string } func newScopeResult(title string, depth int, file string, line int) *ScopeResult { self := new(ScopeResult) self.Title = title self.Depth = depth self.File = file self.Line = line self.Assertions = []*AssertionResult{} return self } /////////////////// StoryReport ///////////////////// type StoryReport struct { Test T Name string File string Line int } func NewStoryReport(test T) *StoryReport { file, line, name := gotest.ResolveExternalCaller() name = removePackagePath(name) self := new(StoryReport) self.Test = test self.Name = name self.File = file self.Line = line return self } // name comes in looking like "github.com/smartystreets/goconvey/examples.TestName". // We only want the stuff after the last '.', which is the name of the test function. func removePackagePath(name string) string { parts := strings.Split(name, ".") return parts[len(parts)-1] } /////////////////// FailureView //////////////////////// // FailureView is also declared in github.com/smarty/assertions. // The json struct tags should be equal in both declarations. type FailureView struct { Message string `json:"Message"` Expected string `json:"Expected"` Actual string `json:"Actual"` } ////////////////////AssertionResult ////////////////////// type AssertionResult struct { File string Line int Expected string Actual string Failure string Error any StackTrace string Skipped bool } func NewFailureReport(failure string, showStack bool) *AssertionResult { report := new(AssertionResult) report.File, report.Line = caller() if showStack { report.StackTrace = stackTrace() } parseFailure(failure, report) return report } func parseFailure(failure string, report *AssertionResult) { view := new(FailureView) err := json.Unmarshal([]byte(failure), view) if err == nil { report.Failure = view.Message report.Expected = view.Expected report.Actual = view.Actual } else { report.Failure = failure } } func NewErrorReport(err any) *AssertionResult { report := new(AssertionResult) report.File, report.Line = caller() report.StackTrace = fullStackTrace() report.Error = fmt.Sprintf("%v", err) return report } func NewSuccessReport() *AssertionResult { return new(AssertionResult) } func NewSkipReport() *AssertionResult { report := new(AssertionResult) report.File, report.Line = caller() report.StackTrace = fullStackTrace() report.Skipped = true return report } func caller() (file string, line int) { file, line, _ = gotest.ResolveExternalCaller() return } func stackTrace() string { buffer := make([]byte, 1024*64) n := runtime.Stack(buffer, false) return removeInternalEntries(string(buffer[:n])) } func fullStackTrace() string { buffer := make([]byte, 1024*64) n := runtime.Stack(buffer, true) return removeInternalEntries(string(buffer[:n])) } func removeInternalEntries(stack string) string { lines := strings.Split(stack, newline) filtered := []string{} for _, line := range lines { if !isExternal(line) { filtered = append(filtered, line) } } return strings.Join(filtered, newline) } func isExternal(line string) bool { for _, p := range internalPackages { if strings.Contains(line, p) { return true } } return false } // NOTE: any new packages that host goconvey packages will need to be added here! // An alternative is to scan the goconvey directory and then exclude stuff like // the examples package but that's nasty too. var internalPackages = []string{ "goconvey/assertions", "goconvey/convey", "goconvey/execution", "goconvey/gotest", "goconvey/reporting", } ================================================ FILE: convey/reporting/statistics.go ================================================ package reporting import ( "fmt" "sync" ) func (self *statistics) BeginStory(story *StoryReport) {} func (self *statistics) Enter(scope *ScopeReport) {} func (self *statistics) Report(report *AssertionResult) { self.Lock() defer self.Unlock() if !self.failing && report.Failure != "" { self.failing = true } if !self.erroring && report.Error != nil { self.erroring = true } if report.Skipped { self.skipped += 1 } else { self.total++ } } func (self *statistics) Exit() {} func (self *statistics) EndStory() { self.Lock() defer self.Unlock() if !self.suppressed { self.printSummaryLocked() } } func (self *statistics) Suppress() { self.Lock() defer self.Unlock() self.suppressed = true } func (self *statistics) PrintSummary() { self.Lock() defer self.Unlock() self.printSummaryLocked() } func (self *statistics) printSummaryLocked() { self.reportAssertionsLocked() self.reportSkippedSectionsLocked() self.completeReportLocked() } func (self *statistics) reportAssertionsLocked() { self.decideColorLocked() self.out.Print("\n%d total %s", self.total, plural("assertion", self.total)) } func (self *statistics) decideColorLocked() { if self.failing && !self.erroring { fmt.Print(yellowColor) } else if self.erroring { fmt.Print(redColor) } else { fmt.Print(greenColor) } } func (self *statistics) reportSkippedSectionsLocked() { if self.skipped > 0 { fmt.Print(yellowColor) self.out.Print(" (one or more sections skipped)") } } func (self *statistics) completeReportLocked() { fmt.Print(resetColor) self.out.Print("\n") self.out.Print("\n") } func (self *statistics) Write(content []byte) (written int, err error) { return len(content), nil // no-op } func NewStatisticsReporter(out *Printer) *statistics { self := statistics{} self.out = out return &self } type statistics struct { sync.Mutex out *Printer total int failing bool erroring bool skipped int suppressed bool } func plural(word string, count int) string { if count == 1 { return word } return word + "s" } ================================================ FILE: convey/reporting/story.go ================================================ // TODO: in order for this reporter to be completely honest // we need to retrofit to be more like the json reporter such that: // 1. it maintains ScopeResult collections, which count assertions // 2. it reports only after EndStory(), so that all tick marks // are placed near the appropriate title. // 3. Under unit test package reporting import ( "fmt" "strings" ) type story struct { out *Printer titlesById map[string]string currentKey []string } func (self *story) BeginStory(story *StoryReport) {} func (self *story) Enter(scope *ScopeReport) { self.out.Indent() self.currentKey = append(self.currentKey, scope.Title) ID := strings.Join(self.currentKey, "|") if _, found := self.titlesById[ID]; !found { self.out.Println("") self.out.Print(scope.Title) self.out.Insert(" ") self.titlesById[ID] = scope.Title } } func (self *story) Report(report *AssertionResult) { if report.Error != nil { fmt.Print(redColor) self.out.Insert(error_) } else if report.Failure != "" { fmt.Print(yellowColor) self.out.Insert(failure) } else if report.Skipped { fmt.Print(yellowColor) self.out.Insert(skip) } else { fmt.Print(greenColor) self.out.Insert(success) } fmt.Print(resetColor) } func (self *story) Exit() { self.out.Dedent() self.currentKey = self.currentKey[:len(self.currentKey)-1] } func (self *story) EndStory() { self.titlesById = make(map[string]string) self.out.Println("\n") } func (self *story) Write(content []byte) (written int, err error) { return len(content), nil // no-op } func NewStoryReporter(out *Printer) *story { self := new(story) self.out = out self.titlesById = make(map[string]string) return self } ================================================ FILE: convey/reporting_hooks_test.go ================================================ package convey import ( "fmt" "net/http" "net/http/httptest" "path" "runtime" "strconv" "strings" "testing" "github.com/smartystreets/goconvey/convey/reporting" ) func TestSingleScopeReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { So(1, ShouldEqual, 1) }) expectEqual(t, "Begin|A|Success|Exit|End", myReporter.wholeStory()) } func TestNestedScopeReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { Convey("B", func() { So(1, ShouldEqual, 1) }) }) expectEqual(t, "Begin|A|B|Success|Exit|Exit|End", myReporter.wholeStory()) } func TestFailureReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { So(1, ShouldBeNil) }) expectEqual(t, "Begin|A|Failure|Exit|End", myReporter.wholeStory()) } func TestFirstFailureEndsScopeExecution(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { So(1, ShouldBeNil) So(nil, ShouldBeNil) }) expectEqual(t, "Begin|A|Failure|Exit|End", myReporter.wholeStory()) } func TestComparisonFailureDeserializedAndReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { So("hi", ShouldEqual, "bye") }) expectEqual(t, `Begin|A|Failure("bye"/"hi")|Exit|End`, myReporter.wholeStory()) } func TestNestedFailureReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { Convey("B", func() { So(2, ShouldBeNil) }) }) expectEqual(t, "Begin|A|B|Failure|Exit|Exit|End", myReporter.wholeStory()) } func TestSuccessAndFailureReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { So(nil, ShouldBeNil) So(1, ShouldBeNil) }) expectEqual(t, "Begin|A|Success|Failure|Exit|End", myReporter.wholeStory()) } func TestIncompleteActionReportedAsSkipped(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { Convey("B", nil) }) expectEqual(t, "Begin|A|B|Skipped|Exit|Exit|End", myReporter.wholeStory()) } func TestSkippedConveyReportedAsSkipped(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { SkipConvey("B", func() { So(1, ShouldEqual, 1) }) }) expectEqual(t, "Begin|A|B|Skipped|Exit|Exit|End", myReporter.wholeStory()) } func TestMultipleSkipsAreReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { Convey("0", func() { So(nil, ShouldBeNil) }) SkipConvey("1", func() {}) SkipConvey("2", func() {}) Convey("3", nil) Convey("4", nil) Convey("5", func() { So(nil, ShouldBeNil) }) }) expected := "Begin" + "|A|0|Success|Exit|Exit" + "|A|1|Skipped|Exit|Exit" + "|A|2|Skipped|Exit|Exit" + "|A|3|Skipped|Exit|Exit" + "|A|4|Skipped|Exit|Exit" + "|A|5|Success|Exit|Exit" + "|End" expectEqual(t, expected, myReporter.wholeStory()) } func TestSkippedAssertionIsNotReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { SkipSo(1, ShouldEqual, 1) }) expectEqual(t, "Begin|A|Skipped|Exit|End", myReporter.wholeStory()) } func TestMultipleSkippedAssertionsAreNotReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { SkipSo(1, ShouldEqual, 1) So(1, ShouldEqual, 1) SkipSo(1, ShouldEqual, 1) }) expectEqual(t, "Begin|A|Skipped|Success|Skipped|Exit|End", myReporter.wholeStory()) } func TestErrorByManualPanicReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { panic("Gopher alert!") }) expectEqual(t, "Begin|A|Error|Exit|End", myReporter.wholeStory()) } func TestIterativeConveysReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { for x := 0; x < 3; x++ { Convey(strconv.Itoa(x), func() { So(x, ShouldEqual, x) }) } }) expectEqual(t, "Begin|A|0|Success|Exit|Exit|A|1|Success|Exit|Exit|A|2|Success|Exit|Exit|End", myReporter.wholeStory()) } func TestNestedIterativeConveysReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func() { for x := 0; x < 3; x++ { Convey(strconv.Itoa(x), func() { for y := 0; y < 3; y++ { Convey("< "+strconv.Itoa(y), func() { So(x, ShouldBeLessThan, y) }) } }) } }) expectEqual(t, ("Begin|" + "A|0|< 0|Failure|Exit|Exit|Exit|" + "A|0|< 1|Success|Exit|Exit|Exit|" + "A|0|< 2|Success|Exit|Exit|Exit|" + "A|1|< 0|Failure|Exit|Exit|Exit|" + "A|1|< 1|Failure|Exit|Exit|Exit|" + "A|1|< 2|Success|Exit|Exit|Exit|" + "A|2|< 0|Failure|Exit|Exit|Exit|" + "A|2|< 1|Failure|Exit|Exit|Exit|" + "A|2|< 2|Failure|Exit|Exit|Exit|" + "End"), myReporter.wholeStory()) } func TestEmbeddedAssertionReported(t *testing.T) { myReporter, test := setupFakeReporter() Convey("A", test, func(c C) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.So(r.FormValue("msg"), ShouldEqual, "ping") })) http.DefaultClient.Get(ts.URL + "?msg=ping") }) expectEqual(t, "Begin|A|Success|Exit|End", myReporter.wholeStory()) } func TestEmbeddedContextHelperReported(t *testing.T) { myReporter, test := setupFakeReporter() helper := func(c C) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Convey("Embedded", func() { So(r.FormValue("msg"), ShouldEqual, "ping") }) }) } Convey("A", test, func(c C) { ts := httptest.NewServer(helper(c)) http.DefaultClient.Get(ts.URL + "?msg=ping") }) expectEqual(t, "Begin|A|Embedded|Success|Exit|Exit|End", myReporter.wholeStory()) } func expectEqual(t *testing.T, expected any, actual any) { if expected != actual { _, file, line, _ := runtime.Caller(1) t.Errorf("Expected '%v' to be '%v' but it wasn't. See '%s' at line %d.", actual, expected, path.Base(file), line) } } func setupFakeReporter() (*fakeReporter, *fakeGoTest) { myReporter := new(fakeReporter) myReporter.calls = []string{} testReporter = myReporter return myReporter, new(fakeGoTest) } type fakeReporter struct { calls []string } func (self *fakeReporter) BeginStory(story *reporting.StoryReport) { self.calls = append(self.calls, "Begin") } func (self *fakeReporter) Enter(scope *reporting.ScopeReport) { self.calls = append(self.calls, scope.Title) } func (self *fakeReporter) Report(report *reporting.AssertionResult) { if report.Error != nil { self.calls = append(self.calls, "Error") } else if report.Failure != "" { message := "Failure" if report.Expected != "" || report.Actual != "" { message += fmt.Sprintf("(%s/%s)", report.Expected, report.Actual) } self.calls = append(self.calls, message) } else if report.Skipped { self.calls = append(self.calls, "Skipped") } else { self.calls = append(self.calls, "Success") } } func (self *fakeReporter) Exit() { self.calls = append(self.calls, "Exit") } func (self *fakeReporter) EndStory() { self.calls = append(self.calls, "End") } func (self *fakeReporter) Write(content []byte) (int, error) { return len(content), nil // no-op } func (self *fakeReporter) wholeStory() string { return strings.Join(self.calls, "|") } //////////////////////////////// type fakeGoTest struct{} func (self *fakeGoTest) Fail() {} func (self *fakeGoTest) Fatalf(format string, args ...any) {} var test t = new(fakeGoTest) ================================================ FILE: convey/stack_trace_test.go ================================================ package convey import ( "fmt" "regexp" "strings" "testing" "github.com/smartystreets/goconvey/convey/reporting" ) var goroutineRE = regexp.MustCompile(`goroutine \d+ \[`) // countGoroutines takes a test output file and counts the number of goroutines // that were mentioned inside it. This does this by hunting for lines such as // "goroutine 42 [running]", while excluding secondary mentions of already-counted // goroutines. func countGoroutines(testOutput string) int { return len(goroutineRE.FindAllStringSubmatch(testOutput, -1)) } func TestStackTrace(t *testing.T) { file, test := setupFileReporter() Convey("A", test, func() { So(1, ShouldEqual, 2) }) if !strings.Contains(file.String(), "Failures:\n") { t.Errorf("Expected errors, found none.") } if strings.Contains(file.String(), "goroutine ") { t.Errorf("Found stack trace, expected none.") } Convey("A", test, StackFail, func() { So(1, ShouldEqual, 2) }) if !strings.Contains(file.String(), "goroutine ") { t.Errorf("Expected stack trace, found none.") } } func TestSetDefaultStackMode(t *testing.T) { file, test := setupFileReporter() SetDefaultStackMode(StackFail) // the default is normally StackError defer SetDefaultStackMode(StackError) Convey("A", test, func() { So(1, ShouldEqual, 2) }) if !strings.Contains(file.String(), "goroutine ") { t.Errorf("Expected stack trace, found none.") } } func TestStackModeMultipleInvocationInheritance(t *testing.T) { file, test := setupFileReporter() // initial convey should default to StaskError, so no stack trace Convey("A", test, FailureContinues, func() { So(1, ShouldEqual, 2) // nested convey has explicit StaskFail, so should emit stack trace Convey("B", StackFail, func() { So(1, ShouldEqual, 2) }) }) stackCount := countGoroutines(file.String()) if stackCount != 1 { t.Errorf("Expected 1 stack trace, found %d.", stackCount) fmt.Printf("RESULT: %s \n", file.String()) } } func TestStackModeMultipleInvocationInheritance2(t *testing.T) { file, test := setupFileReporter() // Explicit StackFail, expect stack trace Convey("A", test, FailureContinues, StackFail, func() { So(1, ShouldEqual, 2) // Nested Convey inherits StackFail, expect stack trace Convey("B", func() { So(1, ShouldEqual, 2) }) }) stackCount := countGoroutines(file.String()) if stackCount != 2 { t.Errorf("Expected 2 stack traces, found %d.", stackCount) } } func TestStackModeMultipleInvocationInheritance3(t *testing.T) { file, test := setupFileReporter() // Explicit StackFail, expect stack trace Convey("A", test, FailureContinues, StackFail, func() { So(1, ShouldEqual, 2) // Nested Convey explicitly sets StackError, so no stack trace Convey("B", StackError, func() { So(1, ShouldEqual, 2) }) }) stackCount := countGoroutines(file.String()) if stackCount != 1 { t.Errorf("Expected 1 stack trace1, found %d.", stackCount) } } func setupFileReporter() (*memoryFile, *fakeGoTest) { //monochrome() file := newMemoryFile() printer := reporting.NewPrinter(file) reporter := reporting.NewProblemReporter(printer) testReporter = reporter return file, new(fakeGoTest) } ////////////////// memoryFile //////////////////// type memoryFile struct { buffer string } func (mf *memoryFile) Write(p []byte) (n int, err error) { mf.buffer += string(p) return len(p), nil } func (mf *memoryFile) String() string { return mf.buffer } func newMemoryFile() *memoryFile { return new(memoryFile) } // func monochrome() { // greenColor, yellowColor, redColor, resetColor = "", "", "", "" // } ================================================ FILE: convey/story_conventions_test.go ================================================ package convey import ( "reflect" "testing" ) func expectPanic(t *testing.T, f string) any { r := recover() if r != nil { if cp, ok := r.(*conveyErr); ok { if cp.fmt != f { t.Error("Incorrect panic message.") } } else { t.Errorf("Incorrect panic type. %s", reflect.TypeOf(r)) } } else { t.Error("Expected panic but none occurred") } return r } func TestMissingTopLevelGoTestReferenceCausesPanic(t *testing.T) { output := map[string]bool{} defer expectEqual(t, false, output["good"]) defer expectPanic(t, missingGoTest) Convey("Hi", func() { output["bad"] = true // this shouldn't happen }) } func TestMissingTopLevelGoTestReferenceAfterGoodExample(t *testing.T) { output := map[string]bool{} defer func() { expectEqual(t, true, output["good"]) expectEqual(t, false, output["bad"]) }() defer expectPanic(t, missingGoTest) Convey("Good example", t, func() { output["good"] = true }) Convey("Bad example", func() { output["bad"] = true // shouldn't happen }) } func TestExtraReferencePanics(t *testing.T) { output := map[string]bool{} defer expectEqual(t, false, output["bad"]) defer expectPanic(t, extraGoTest) Convey("Good example", t, func() { Convey("Bad example - passing in *testing.T a second time!", t, func() { output["bad"] = true // shouldn't happen }) }) } func TestParseRegistrationMissingRequiredElements(t *testing.T) { defer expectPanic(t, parseError) Convey() } func TestParseRegistration_MissingNameString(t *testing.T) { defer expectPanic(t, parseError) Convey(func() {}) } func TestParseRegistration_MissingActionFunc(t *testing.T) { defer expectPanic(t, parseError) Convey("Hi there", 12345) } func TestFailureModeNoContext(t *testing.T) { Convey("Foo", t, func() { done := make(chan int, 1) go func() { defer func() { done <- 1 }() defer expectPanic(t, noStackContext) So(len("I have no context"), ShouldBeGreaterThan, 0) }() <-done }) } func TestFailureModeDuplicateSuite(t *testing.T) { Convey("cool", t, func() { defer expectPanic(t, multipleIdenticalConvey) Convey("dup", nil) Convey("dup", nil) }) } func TestFailureModeIndeterminentSuiteNames(t *testing.T) { defer expectPanic(t, differentConveySituations) name := "bob" Convey("cool", t, func() { for i := 0; i < 3; i++ { Convey(name, func() {}) name += "bob" } }) } func TestFailureModeNestedIndeterminentSuiteNames(t *testing.T) { defer expectPanic(t, differentConveySituations) name := "bob" Convey("cool", t, func() { Convey("inner", func() { for i := 0; i < 3; i++ { Convey(name, func() {}) name += "bob" } }) }) } func TestFailureModeParameterButMissing(t *testing.T) { defer expectPanic(t, parseError) prepare() Convey("Foobar", t, FailureHalts) } func TestFailureModeParameterWithAction(t *testing.T) { prepare() Convey("Foobar", t, FailureHalts, func() {}) } func TestExtraConveyParameters(t *testing.T) { defer expectPanic(t, parseError) prepare() Convey("Foobar", t, FailureHalts, func() {}, "This is not supposed to be here") } func TestExtraConveyParameters2(t *testing.T) { defer expectPanic(t, parseError) prepare() Convey("Foobar", t, func() {}, "This is not supposed to be here") } func TestExtraConveyParameters3(t *testing.T) { defer expectPanic(t, parseError) output := prepare() Convey("A", t, func() { output += "A " Convey("B", func() { output += "B " }, "This is not supposed to be here") }) expectEqual(t, "A ", output) } ================================================ FILE: convey/update_assertions.sh ================================================ #!/usr/bin/env bash cd "$(dirname $(realpath $0))" ASSERTIONS=($( go tool nm "$(go list -export -f '{{.Export}}' github.com/smarty/assertions)" |\ awk '/ T github\.com\/smarty\/assertions\.Should/{split($3, a, "."); print a[3]}' |\ sort | uniq)) ( echo "package convey" echo echo "// DO NOT EDIT: generated by update_assertions.sh" echo echo "//go:generate ./update_assertions.sh" echo echo "import \"github.com/smarty/assertions\"" echo echo "// These assertions are forwarded from github.com/smarty/assertions" echo "// in order to make convey self-contained." echo "var (" for assertion in "${ASSERTIONS[@]}" do echo " $assertion = assertions.$assertion" done echo ")" ) > ./assertions.go go fmt ./assertions.go ================================================ FILE: examples/assertion_examples_test.go ================================================ package examples import ( "bytes" "io" "testing" "time" . "github.com/smartystreets/goconvey/convey" ) func TestAssertionsAreAvailableFromConveyPackage(t *testing.T) { SetDefaultFailureMode(FailureContinues) defer SetDefaultFailureMode(FailureHalts) Convey("Equality assertions should be accessible", t, func() { thing1a := thing{a: "asdf"} thing1b := thing{a: "asdf"} thing2 := thing{a: "qwer"} So(1, ShouldEqual, 1) So(1, ShouldNotEqual, 2) So(1, ShouldAlmostEqual, 1.000000000000001) So(1, ShouldNotAlmostEqual, 2, 0.5) So(thing1a, ShouldResemble, thing1b) So(thing1a, ShouldNotResemble, thing2) So(&thing1a, ShouldPointTo, &thing1a) So(&thing1a, ShouldNotPointTo, &thing1b) So(nil, ShouldBeNil) So(1, ShouldNotBeNil) So(true, ShouldBeTrue) So(false, ShouldBeFalse) So(0, ShouldBeZeroValue) So(1, ShouldNotBeZeroValue) }) Convey("Numeric comparison assertions should be accessible", t, func() { So(1, ShouldBeGreaterThan, 0) So(1, ShouldBeGreaterThanOrEqualTo, 1) So(1, ShouldBeLessThan, 2) So(1, ShouldBeLessThanOrEqualTo, 1) So(1, ShouldBeBetween, 0, 2) So(1, ShouldNotBeBetween, 2, 4) So(1, ShouldBeBetweenOrEqual, 1, 2) So(1, ShouldNotBeBetweenOrEqual, 2, 4) }) Convey("Container assertions should be accessible", t, func() { So([]int{1, 2, 3}, ShouldContain, 2) So([]int{1, 2, 3}, ShouldNotContain, 4) So(map[int]int{1: 1, 2: 2, 3: 3}, ShouldContainKey, 2) So(map[int]int{1: 1, 2: 2, 3: 3}, ShouldNotContainKey, 4) So(1, ShouldBeIn, []int{1, 2, 3}) So(4, ShouldNotBeIn, []int{1, 2, 3}) So([]int{}, ShouldBeEmpty) So([]int{1}, ShouldNotBeEmpty) So([]int{1, 2}, ShouldHaveLength, 2) }) Convey("String assertions should be accessible", t, func() { So("asdf", ShouldStartWith, "a") So("asdf", ShouldNotStartWith, "z") So("asdf", ShouldEndWith, "df") So("asdf", ShouldNotEndWith, "as") So("", ShouldBeBlank) So("asdf", ShouldNotBeBlank) So("asdf", ShouldContainSubstring, "sd") So("asdf", ShouldNotContainSubstring, "af") }) Convey("Panic recovery assertions should be accessible", t, func() { So(panics, ShouldPanic) So(func() {}, ShouldNotPanic) So(panics, ShouldPanicWith, "Goofy Gophers!") So(panics, ShouldNotPanicWith, "Guileless Gophers!") }) Convey("Type-checking assertions should be accessible", t, func() { // NOTE: Values or pointers may be checked. If a value is passed, // it will be cast as a pointer to the value to avoid cases where // the struct being tested takes pointer receivers. Go allows values // or pointers to be passed as receivers on methods with a value // receiver, but only pointers on methods with pointer receivers. // See: // http://golang.org/doc/effective_go.html#pointers_vs_values // http://golang.org/doc/effective_go.html#blank_implements // http://blog.golang.org/laws-of-reflection So(1, ShouldHaveSameTypeAs, 0) So(1, ShouldNotHaveSameTypeAs, "1") So(bytes.NewBufferString(""), ShouldImplement, (*io.Reader)(nil)) So("string", ShouldNotImplement, (*io.Reader)(nil)) }) Convey("Time assertions should be accessible", t, func() { january1, _ := time.Parse(timeLayout, "2013-01-01 00:00") january2, _ := time.Parse(timeLayout, "2013-01-02 00:00") january3, _ := time.Parse(timeLayout, "2013-01-03 00:00") january4, _ := time.Parse(timeLayout, "2013-01-04 00:00") january5, _ := time.Parse(timeLayout, "2013-01-05 00:00") oneDay, _ := time.ParseDuration("24h0m0s") So(january1, ShouldHappenBefore, january4) So(january1, ShouldHappenOnOrBefore, january1) So(january2, ShouldHappenAfter, january1) So(january2, ShouldHappenOnOrAfter, january2) So(january3, ShouldHappenBetween, january2, january5) So(january3, ShouldHappenOnOrBetween, january3, january5) So(january1, ShouldNotHappenOnOrBetween, january2, january5) So(january2, ShouldHappenWithin, oneDay, january3) So(january5, ShouldNotHappenWithin, oneDay, january1) So([]time.Time{january1, january2}, ShouldBeChronological) }) } type thing struct { a string } func panics() { panic("Goofy Gophers!") } const timeLayout = "2006-01-02 15:04" ================================================ FILE: examples/bowling_game.go ================================================ package examples // Game contains the state of a bowling game. type Game struct { rolls []int current int } // NewGame allocates and starts a new game of bowling. func NewGame() *Game { game := new(Game) game.rolls = make([]int, maxThrowsPerGame) return game } // Roll rolls the ball and knocks down the number of pins specified by pins. func (self *Game) Roll(pins int) { self.rolls[self.current] = pins self.current++ } // Score calculates and returns the player's current score. func (self *Game) Score() (sum int) { for throw, frame := 0, 0; frame < framesPerGame; frame++ { if self.isStrike(throw) { sum += self.strikeBonusFor(throw) throw += 1 } else if self.isSpare(throw) { sum += self.spareBonusFor(throw) throw += 2 } else { sum += self.framePointsAt(throw) throw += 2 } } return sum } // isStrike determines if a given throw is a strike or not. A strike is knocking // down all pins in one throw. func (self *Game) isStrike(throw int) bool { return self.rolls[throw] == allPins } // strikeBonusFor calculates and returns the strike bonus for a throw. func (self *Game) strikeBonusFor(throw int) int { return allPins + self.framePointsAt(throw+1) } // isSpare determines if a given frame is a spare or not. A spare is knocking // down all pins in one frame with two throws. func (self *Game) isSpare(throw int) bool { return self.framePointsAt(throw) == allPins } // spareBonusFor calculates and returns the spare bonus for a throw. func (self *Game) spareBonusFor(throw int) int { return allPins + self.rolls[throw+2] } // framePointsAt computes and returns the score in a frame specified by throw. func (self *Game) framePointsAt(throw int) int { return self.rolls[throw] + self.rolls[throw+1] } const ( // allPins is the number of pins allocated per fresh throw. allPins = 10 // framesPerGame is the number of frames per bowling game. framesPerGame = 10 // maxThrowsPerGame is the maximum number of throws possible in a single game. maxThrowsPerGame = 21 ) ================================================ FILE: examples/bowling_game_test.go ================================================ /* Reference: http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata See the very first link (which happens to be the very first word of the first paragraph) on the page for a tutorial. */ package examples import ( "testing" . "github.com/smartystreets/goconvey/convey" ) func TestBowlingGameScoring(t *testing.T) { Convey("Given a fresh score card", t, func() { game := NewGame() Convey("When all gutter balls are thrown", func() { game.rollMany(20, 0) Convey("The score should be zero", func() { So(game.Score(), ShouldEqual, 0) }) }) Convey("When all throws knock down only one pin", func() { game.rollMany(20, 1) Convey("The score should be 20", func() { So(game.Score(), ShouldEqual, 20) }) }) Convey("When a spare is thrown", func() { game.rollSpare() game.Roll(3) game.rollMany(17, 0) Convey("The score should include a spare bonus.", func() { So(game.Score(), ShouldEqual, 16) }) }) Convey("When a strike is thrown", func() { game.rollStrike() game.Roll(3) game.Roll(4) game.rollMany(16, 0) Convey("The score should include a strike bonus.", func() { So(game.Score(), ShouldEqual, 24) }) }) Convey("When all strikes are thrown", func() { game.rollMany(21, 10) Convey("The score should be 300.", func() { So(game.Score(), ShouldEqual, 300) }) }) }) } func (self *Game) rollMany(times, pins int) { for x := 0; x < times; x++ { self.Roll(pins) } } func (self *Game) rollSpare() { self.Roll(5) self.Roll(5) } func (self *Game) rollStrike() { self.Roll(10) } ================================================ FILE: examples/doc.go ================================================ // Package examples contains, well, examples of how to use goconvey to // specify behavior of a system under test. It contains a well-known example // by Robert C. Martin called "Bowling Game Kata" as well as another very // trivial example that demonstrates Reset() and some of the assertions. package examples ================================================ FILE: examples/examples.goconvey ================================================ // Uncomment the next line to disable the package when running the GoConvey UI: //IGNORE // Uncomment the next line to limit testing to the specified test function name pattern: //-run=TestAssertionsAreAvailableFromConveyPackage // Uncomment the next line to limit testing to those tests that don't bail when testing.Short() is true: //-short // include any additional `go test` flags or application-specific flags below: -timeout=1s ================================================ FILE: examples/simple_example_test.go ================================================ package examples import ( "testing" . "github.com/smartystreets/goconvey/convey" ) func TestIntegerManipulation(t *testing.T) { t.Parallel() Convey("Given a starting integer value", t, func() { x := 42 Convey("When incremented", func() { x++ Convey("The value should be greater by one", func() { So(x, ShouldEqual, 43) }) Convey("The value should NOT be what it used to be", func() { So(x, ShouldNotEqual, 42) }) }) Convey("When decremented", func() { x-- Convey("The value should be lesser by one", func() { So(x, ShouldEqual, 41) }) Convey("The value should NOT be what it used to be", func() { So(x, ShouldNotEqual, 42) }) }) }) } ================================================ FILE: go.mod ================================================ module github.com/smartystreets/goconvey go 1.21.7 require ( github.com/jtolds/gls v4.20.0+incompatible github.com/smarty/assertions v1.15.1 golang.org/x/tools v0.18.0 ) require ( github.com/gopherjs/gopherjs v1.17.2 // indirect golang.org/x/mod v0.15.0 // indirect ) ================================================ FILE: go.sum ================================================ github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/smarty/assertions v1.15.1 h1:812oFiXI+G55vxsFf+8bIZ1ux30qtkdqzKbEFwyX3Tk= github.com/smarty/assertions v1.15.1/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= ================================================ FILE: goconvey.go ================================================ // This executable provides an HTTP server that watches for file system changes // to .go files within the working directory (and all nested go packages). // Navigating to the configured host and port in a web browser will display the // latest results of running `go test` in each go package. package main import ( "context" "embed" "flag" "fmt" "io/fs" "io/ioutil" "log" "net" "net/http" "os" "os/exec" "os/signal" "path/filepath" "runtime" "runtime/debug" "strings" "syscall" "time" "golang.org/x/tools/go/packages" "github.com/smartystreets/goconvey/web/server/api" "github.com/smartystreets/goconvey/web/server/contract" "github.com/smartystreets/goconvey/web/server/executor" "github.com/smartystreets/goconvey/web/server/messaging" "github.com/smartystreets/goconvey/web/server/parser" "github.com/smartystreets/goconvey/web/server/system" "github.com/smartystreets/goconvey/web/server/watch" ) func init() { flag.IntVar(&port, "port", 8080, "The port at which to serve http.") flag.StringVar(&host, "host", "127.0.0.1", "The host at which to serve http.") flag.DurationVar(&nap, "poll", quarterSecond, "The interval to wait between polling the file system for changes.") flag.IntVar(¶llelPackages, "packages", 10, "The number of packages to test in parallel. Higher == faster but more costly in terms of computing.") flag.StringVar(&gobin, "gobin", "go", "The path to the 'go' binary (default: search on the PATH).") flag.BoolVar(&cover, "cover", true, "Enable package-level coverage statistics.") flag.IntVar(&depth, "depth", -1, "The directory scanning depth. If -1, scan infinitely deep directory structures. 0: scan working directory. 1+: Scan into nested directories, limited to value.") flag.StringVar(&timeout, "timeout", "0", "The test execution timeout if none is specified in the *.goconvey file (default is '0', which is the same as not providing this option).") flag.StringVar(&watchedSuffixes, "watchedSuffixes", ".go", "A comma separated list of file suffixes to watch for modifications.") flag.StringVar(&excludedDirs, "excludedDirs", "vendor,node_modules", "A comma separated list of directories that will be excluded from being watched.") flag.StringVar(&workDir, "workDir", "", "set goconvey working directory (default current directory).") flag.BoolVar(&autoLaunchBrowser, "launchBrowser", true, "toggle auto launching of browser.") flag.BoolVar(&leakTemp, "leakTemp", false, "leak temp dir with coverage reports.") log.SetOutput(os.Stdout) log.SetFlags(log.LstdFlags | log.Lshortfile) } func main() { flag.Parse() printHeader() tmpDir, err := ioutil.TempDir("", "*.goconvey") if err != nil { log.Fatal(err) } reports := filepath.Join(tmpDir, "coverage_out") if err := os.Mkdir(reports, 0700); err != nil { log.Fatal(err) } if leakTemp { log.Printf("leaking temporary directory %q\n", tmpDir) } else { defer func() { if err := os.RemoveAll(tmpDir); err != nil { log.Printf("failed to clean temporary directory %q: %s\n", tmpDir, err) } }() } done := make(chan os.Signal) signal.Notify(done, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) working := getWorkDir() shell := system.NewShell(gobin, reports, cover, timeout) watcherInput := make(chan messaging.WatcherCommand) watcherOutput := make(chan messaging.Folders) excludedDirItems := strings.Split(excludedDirs, `,`) watcher := watch.NewWatcher(working, depth, nap, watcherInput, watcherOutput, watchedSuffixes, excludedDirItems) parser := parser.NewParser(parser.ParsePackageResults) tester := executor.NewConcurrentTester(shell) tester.SetBatchSize(parallelPackages) longpollChan := make(chan chan string) executor := executor.NewExecutor(tester, parser, longpollChan) server := api.NewHTTPServer(working, watcherInput, executor, longpollChan) listener := createListener() go runTestOnUpdates(watcherOutput, executor, server) go watcher.Listen() if autoLaunchBrowser { go launchBrowser(listener.Addr().String()) } srv := serveHTTP(reports, server, listener) <-done log.Println("shutting down") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Printf("failed to shutdown: %s\n", err) } } func printHeader() { log.Println("GoConvey server: ") serverVersion := "" if binfo, ok := debug.ReadBuildInfo(); ok { serverVersion = binfo.Main.Version } log.Println(" version:", serverVersion) log.Println(" host:", host) log.Println(" port:", port) log.Println(" poll:", nap) log.Println(" cover:", cover) log.Println() } func browserCmd() (string, bool) { browser := map[string]string{ "darwin": "open", "linux": "xdg-open", "windows": "start", } cmd, ok := browser[runtime.GOOS] return cmd, ok } func launchBrowser(addr string) { browser, ok := browserCmd() if !ok { log.Printf("Skipped launching browser for this OS: %s", runtime.GOOS) return } log.Printf("Launching browser on %s", addr) url := fmt.Sprintf("http://%s", addr) cmd := exec.Command(browser, url) output, err := cmd.CombinedOutput() if err != nil { log.Println(err) } log.Println(string(output)) } func runTestOnUpdates(queue chan messaging.Folders, executor contract.Executor, server contract.Server) { for update := range queue { log.Println("Received request from watcher to execute tests...") packages := extractPackages(update) output := executor.ExecuteTests(packages) root := extractRoot(update, packages) server.ReceiveUpdate(root, output) } } func extractPackages(folderList messaging.Folders) []*contract.Package { packageList := []*contract.Package{} for _, folder := range folderList { if isInsideTestdata(folder) { continue } hasImportCycle := testFilesImportTheirOwnPackage(folder.Path) packageName := resolvePackageName(folder.Path) packageList = append( packageList, contract.NewPackage(folder, packageName, hasImportCycle), ) } return packageList } // For packages that operate on Go source code files, such as Go tooling, it is // important to have a location that will not be considered part of package // source to store those files. The official Go tooling selected the testdata // folder for this purpose, so we need to ignore folders inside testdata. func isInsideTestdata(folder *messaging.Folder) bool { relativePath, err := filepath.Rel(folder.Root, folder.Path) if err != nil { // There should never be a folder that's not inside the root, but if // there is, we can presumably count it as outside a testdata folder as // well return false } for _, directory := range strings.Split(filepath.ToSlash(relativePath), "/") { if directory == "testdata" { return true } } return false } func extractRoot(folderList messaging.Folders, packageList []*contract.Package) string { path := packageList[0].Path folder := folderList[path] return folder.Root } func createListener() net.Listener { l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { log.Println(err) } if l == nil { os.Exit(1) } return l } //go:embed web/client var static embed.FS func serveHTTP(reports string, server contract.Server, listener net.Listener) *http.Server { webclient, err := fs.Sub(static, "web/client") if err != nil { log.Fatal(err) } http.Handle("/", http.FileServer(http.FS(webclient))) http.HandleFunc("/watch", server.Watch) http.HandleFunc("/ignore", server.Ignore) http.HandleFunc("/reinstate", server.Reinstate) http.HandleFunc("/latest", server.Results) http.HandleFunc("/execute", server.Execute) http.HandleFunc("/status", server.Status) http.HandleFunc("/status/poll", server.LongPollStatus) http.HandleFunc("/pause", server.TogglePause) http.Handle("/reports/", http.StripPrefix("/reports/", http.FileServer(http.Dir(reports)))) log.Printf("Serving HTTP at: http://%s\n", listener.Addr()) ret := &http.Server{} go func() { err := ret.Serve(listener) if err != nil { log.Println(err) } }() return ret } func exists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } func getWorkDir() string { working := "" var err error if workDir != "" { working = workDir } else { working, err = os.Getwd() if err != nil { log.Fatal(err) } } result, err := exists(working) if err != nil { log.Fatal(err) } if !result { log.Fatalf("Path:%s does not exists", working) } return working } var ( port int host string gobin string nap time.Duration parallelPackages int cover bool depth int timeout string watchedSuffixes string excludedDirs string autoLaunchBrowser bool leakTemp bool quarterSecond = time.Millisecond * 250 workDir string ) const ( separator = string(filepath.Separator) endGoPath = separator + "src" + separator ) // This method exists because of a bug in the go cover tool that // causes an infinite loop when you try to run `go test -cover` // on a package that has an import cycle defined in one of it's // test files. Yuck. func testFilesImportTheirOwnPackage(packagePath string) bool { meta, err := packages.Load( &packages.Config{ Mode: packages.NeedName | packages.NeedImports, Tests: true, }, packagePath, ) if err != nil { return false } testPackageID := fmt.Sprintf("%s [%s.test]", meta[0], meta[0]) for _, testPackage := range meta[1:] { if testPackage.ID != testPackageID { continue } for dependency := range testPackage.Imports { if dependency == meta[0].PkgPath { return true } } break } return false } func resolvePackageName(path string) string { pkg, err := packages.Load( &packages.Config{ Mode: packages.NeedName, }, path, ) if err == nil { return pkg[0].PkgPath } nameArr := strings.Split(path, endGoPath) return nameArr[len(nameArr)-1] } ================================================ FILE: web/client/composer.html ================================================ GoConvey Composer

================================================ FILE: web/client/index.html ================================================ GoConvey
PASS
Controls
NOTICE:

Coverage
Ignored
No Test Functions
No Test Files
No Go Files
Build Failures
Panics
Failures
Stories
LOG
Last test : / /
LIVE REPLAY PAUSED
================================================ FILE: web/client/resources/css/common.css ================================================ /* Eric Meyer's Reset CSS v2.0 */ html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0} @font-face { font-family: 'Open Sans'; src: local("Open Sans"), url("../fonts/Open_Sans/OpenSans-Regular.ttf"); } @font-face { font-family: 'Orbitron'; src: local("Orbitron"), url("../fonts/Orbitron/Orbitron-Regular.ttf"); } @font-face { font-family: 'Oswald'; src: local("Oswald"), url("../fonts/Oswald/Oswald-Regular.ttf"); } ::selection { background: #87AFBC; color: #FFF; text-shadow: none; } ::-moz-selection { background: #87AFBC; color: #FFF; text-shadow: none; } ::-webkit-input-placeholder { font-style: italic; } :-moz-placeholder { font-style: italic; } ::-moz-placeholder { font-style: italic; } :-ms-input-placeholder { font-style: italic; } html, body { height: 100%; min-height: 100%; } body { -webkit-transform: translate3d(0, 0, 0); /* attempts to fix Chrome glitching on Mac */ background-position: fixed; background-repeat: no-repeat; font-family: Menlo, Monaco, 'Courier New', monospace; line-height: 1.5em; font-size: 14px; overflow: hidden; display: none; } a { text-decoration: none; } a:hover { text-decoration: underline; } a.fa { text-decoration: none; } b { font-weight: bold; } i { font-style: italic; } hr { border: 0; background: 0; height: 0; margin: 0; padding: 0; } input[type=text] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; background: none; border: none; border-bottom-width: 1px; border-bottom-style: solid; outline: none; padding-bottom: .1em; font: 300 18px/1.5em 'Open Sans', sans-serif; } .overall { padding: 30px 0 15px; position: relative; z-index: 50; } .status { line-height: 1em; font-family: 'Orbitron', monospace; text-align: center; } .overall .status { font-size: 46px; letter-spacing: 5px; text-transform: uppercase; white-space: nowrap; } .toggler { font-size: 10px; padding: 3px 5px; text-decoration: none; text-transform: uppercase; cursor: pointer; line-height: 1.5em; } .toggler.narrow { display: none; } .togglable { overflow-x: auto; } .controls { font-size: 18px; line-height: 1em; } .controls li { text-decoration: none; display: block; float: left; padding: .75em; cursor: pointer; } .server-down { display: none; text-align: center; padding: 10px 0; } footer .server-down { padding: 8px 15px; text-transform: uppercase; } #logo { font-family: 'Oswald', 'Impact', 'Arial Black', sans-serif; } #path-container { margin-top: .4em; } #path { width: 100%; text-align: center; border-bottom-width: 0; } #path:hover, #path:focus { border-bottom-width: 1px; } .expandable { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; border-top-width: 1px; border-top-style: solid; overflow-y: hidden; overflow-x: auto; text-align: center; white-space: nowrap; display: none; } .settings { white-space: normal; overflow-x: auto; white-space: nowrap; } .settings .setting-meta, .settings .setting-val { display: inline-block; } .settings .container { padding: 15px 0; } .settings .setting { font-size: 13px; display: inline-block; margin-right: 5%; } .settings .setting:first-child { margin-left: 5%; } .settings .setting .setting-meta { text-align: right; padding-right: 1em; vertical-align: middle; max-width: 150px; } .settings .setting .setting-meta small { font-size: 8px; text-transform: uppercase; display: block; line-height: 1.25em; } .history .container { padding: 15px 0 15px 25%; } .history .item { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; transition: all .1s linear; -moz-transition: all .1s linear; -webkit-transition: all .1s linear; -o-transition: all .1s linear; display: inline-block; text-align: left; margin: 0 20px; padding: 20px; height: 100%; width: 175px; opacity: .7; cursor: pointer; } .history .item:hover { opacity: 1; } .history .item:nth-child(odd):hover { -webkit-transform: scale(1.1) rotate(5deg); -moz-transform: scale(1.1) rotate(5deg); } .history .item:nth-child(even):hover { -webkit-transform: scale(1.1) rotate(-5deg); -moz-transform: scale(1.1) rotate(-5deg); } .history .item .summary { font: 14px/1.5em 'Monaco', 'Menlo', 'Courier New', monospace; } .history .item.selected { opacity: 1; } .history .status { font-size: 13px; } .frame { position: relative; z-index: 0; width: 100%; } .frame .col { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; border-right-width: 1px; border-right-style: solid; float: left; height: 100%; overflow-y: auto; } .frame .col:first-child { border-left: none; } .frame .col:last-child { border-right: none; } #col-1 { width: 15%; } #col-2 { width: 60%; } #col-3 { width: 25%; } #coverage { font-size: 10px; white-space: nowrap; } #coverage-color-template { display: none; } .rtl { direction: rtl; } .pkg-cover { position: relative; } .pkg-cover a { color: inherit !important; text-decoration: none; } .pkg-cover-bar { position: absolute; top: 0; left: 0; height: 100%; z-index: 1; } .pkg-cover-name { position: relative; z-index: 2; } .pkg-cover-name, .pkg-list { font-family: 'Menlo', monospace; font-size: 10px; padding-right: 2%; white-space: nowrap; } .buildfail-pkg, .panic-pkg, .failure-pkg { padding: 5px 10px; font: 14px 'Open Sans', sans-serif; } .buildfail-output, .panic-output, .failure-output { padding: 10px; font-size: 12px; line-height: 1.25em; overflow-y: auto; white-space: pre-wrap; font-family: 'Menlo', monospace; } .panic-story, .failure-story { font-size: 10px; line-height: 1.25em; font-family: 'Open Sans', sans-serif; } .panic-summary { font-size: 14px; font-weight: bold; line-height: 1.5em; } .panic-file, .failure-file { font-size: 13px; line-height: 1.5em; } .diffviewer { border-collapse: collapse; width: 100%; } .diffviewer td { border-bottom-width: 1px; border-bottom-style: solid; padding: 2px 5px; font-size: 14px; } .diffviewer .original, .diffviewer .changed, .diffviewer .diff { white-space: pre-wrap; } .diffviewer tr:first-child td { border-top-width: 1px; border-top-style: solid; } .diffviewer td:first-child { width: 65px; font-size: 10px; border-right-width: 1px; border-right-style: solid; text-transform: uppercase; } .diff ins { text-decoration: none; } #stories table { width: 100%; } .story-pkg { cursor: pointer; } .story-pkg td { font: 16px 'Open Sans', sans-serif; white-space: nowrap; padding: 10px; } .story-pkg td:first-child { width: 1em; } .story-line { font: 12px 'Open Sans', sans-serif; cursor: default; } .story-line td { padding-top: 7px; padding-bottom: 7px; } .pkg-toggle-container { position: relative; display: inline-block; } .toggle-all-pkg { font-size: 10px; text-transform: uppercase; position: absolute; padding: 5px; font-family: 'Menlo', 'Open Sans', sans-serif; display: none; } .story-line-summary-container { padding: 0 10px 0 10px; white-space: nowrap; width: 35px; text-align: center; } .story-line-status { width: 6px; min-width: 6px; height: 100%; } .story-line-desc { padding: 5px; } .story-line-desc .message { font-family: 'Menlo', monospace; white-space: pre-wrap; } .statusicon { font: 14px 'Open Sans', sans-serif; } .statusicon.skip { font-size: 16px; } .depth-0 { padding-left: 1.5em !important; } .depth-1 { padding-left: 3em !important; } .depth-2 { padding-left: 4.5em !important; } .depth-3 { padding-left: 6em !important; } .depth-4 { padding-left: 7.5em !important; } .depth-5 { padding-left: 9em !important; } .depth-6 { padding-left: 10.5em !important; } .depth-7 { padding-left: 11em !important; } .log { font-size: 11px; line-height: 1.5em; padding: 5px; padding-bottom: .5em; } .log .line { white-space: pre-wrap; padding-left: 2em; text-indent: -2em; } footer { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; position: absolute; bottom: 0; left: 0; padding: 5px 15px; width: 100%; border-top-width: 1px; border-top-style: solid; font-size: 12px; } footer section { float: left; } footer section:first-child { width: 80%; } footer section:last-child { text-align: right; width: 20%; } footer .info { padding: 0 10px; } footer .info:first-child { padding-left: 0; } #narrow-summary { display: none; } footer .replay, footer .paused { display: none; } footer .replay { cursor: pointer; } footer .server-down .notice-message { font-size: 10px; } .rel { position: relative; } .text-right { text-align: right; } .text-center { text-align: center; } .text-left { text-align: left; } .float-left { float: left; } .float-right { float: right; } .clear { clear: both; } .nowrap { white-space: nowrap; } .clr-blue { color: #2B597F; } .show { display: block; } .hide { display: none; } .enum { cursor: pointer; display: inline-block; font-size: 12px; border-width: 1px; border-style: solid; border-radius: 9px; vertical-align: middle; } .enum > li { display: block; float: left; padding: 5px 12px; border-left-width: 1px; border-left-style: solid; } .enum > li:first-child { border-left: 0px; border-top-left-radius: 8px; border-bottom-left-radius: 8px; } .enum > li:last-child { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } .disabled { cursor: default !important; background: transparent !important; } .spin-once { -webkit-animation: fa-spin 0.5s 1 ease; animation: fa-spin 0.5s 1 ease; } .spin-slowly { -webkit-animation: fa-spin .75s infinite linear; animation: fa-spin .75s infinite linear; } .throb { -webkit-animation: throb 2.5s ease-in-out infinite; -moz-animation: throb 2.5s ease-in-out infinite; -o-animation: throb 2.5s ease-in-out infinite; animation: throb 2.5s ease-in-out infinite; } .flash { -webkit-animation: flash 4s linear infinite; -moz-animation: flash 4s linear infinite; -o-animation: flash 4s linear infinite; animation: flash 4s linear infinite; } /* Clearfix */ .cf:before, .cf:after { content: " "; display: table; } .cf:after { clear: both; } @media (max-width: 1099px) { #col-1 { width: 25%; } #col-2 { width: 75%; border-right: none; } #col-3 { display: none; } footer #duration { display: none; } } @media (max-width: 900px) { footer #last-test-container { display: none; } } @media (min-width: 850px) and (max-width: 1220px) { #path { font-size: 14px; margin-top: 5px; } } @media (min-width: 700px) and (max-width: 849px) { #path { font-size: 12px; margin-top: 8px; } } @media (max-width: 799px) { #col-1 { display: none; } #col-2 { width: 100%; } #stories .story-pkg-name { font-size: 14px; } #stories .story-pkg-watch-td { display: none; } } @media (max-width: 700px) { #path-container { display: none; } footer #time { display: none; } footer .info { padding: 0 5px; } footer .server-down .notice-message { display: none; } } @media (max-width: 499px) { .toggler.narrow { display: block; } #show-gen { display: none; } .hide-narrow { display: none; } .show-narrow { display: block; } .overall .status { font-size: 28px; letter-spacing: 1px; } .toggler { display: block; } .controls ul { text-align: center; float: none; } .controls li { display: inline-block; float: none; } .enum > li { float: left; display: block; } #logo { display: none; } .history .item { margin: 0 5px; } .history .item .summary { display: none; } .server-down { font-size: 14px; } #stories .story-pkg-name { font-size: 16px; } #stories .not-pkg-name { display: none; } footer #duration { display: none; } footer #summary { display: none; } footer #narrow-summary { display: inline; } } /** Custom CSS Animations **/ @-webkit-keyframes throb { 0% { opacity: 1; } 50% { opacity: .35; } 100% { opacity: 1; } } @-moz-keyframes throb { 0% { opacity: 1; } 50% { opacity: .35; } 100% { opacity: 1; } } @-o-keyframes throb { 0% { opacity: 1; } 50% { opacity: .35; } 100% { opacity: 1; } } @keyframes throb { 0% { opacity: 1; } 50% { opacity: .35; } 100% { opacity: 1; } } @-webkit-keyframes flash { 70% { opacity: 1; } 90% { opacity: 0; } 98% { opacity: 0; } 100% { opacity: 1; } } @-moz-keyframes flash { 70% { opacity: 1; } 90% { opacity: 0; } 98% { opacity: 0; } 100% { opacity: 1; } } @-o-keyframes flash { 70% { opacity: 1; } 90% { opacity: 0; } 98% { opacity: 0; } 100% { opacity: 1; } } @keyframes flash { 70% { opacity: 1; } 90% { opacity: 0; } 98% { opacity: 0; } 100% { opacity: 1; } } /* #coverage { perspective: 1000; } #coverage .pkg-cover { -webkit-transition: .7s; transform-style: preserve-3d; position: relative; } #coverage:hover .pkg-cover { -webkit-transform: rotateX(180deg); }*/ ================================================ FILE: web/client/resources/css/composer.css ================================================ /* Eric Meyer's Reset CSS v2.0 */ html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0} @font-face { font-family: 'Open Sans'; src: local("Open Sans"), url("../fonts/Open_Sans/OpenSans-Regular.ttf"); } @font-face { font-family: 'Oswald'; src: local("Oswald"), url("../fonts/Oswald/Oswald-Regular.ttf"); } body { font-family: 'Open Sans', 'Helvetica Neue', sans-serif; font-size: 16px; } header { background: #2C3F49; padding: 10px; } .logo { font-family: Oswald, sans-serif; font-size: 24px; margin-right: 5px; color: #DDD; } .afterlogo { font-size: 12px; text-transform: uppercase; position: relative; top: -3px; color: #999; } #input, #output { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 15px; height: 80%; float: left; overflow: auto; } #input { border: 0; font: 300 18px/1.5em 'Open Sans'; resize: none; outline: none; width: 50%; } #output { width: 50%; display: inline-block; background: #F0F0F0; font: 14px/1.25em 'Menlo', 'Monaco', 'Courier New', monospace; border-left: 1px solid #CCC; white-space: pre-wrap; } ================================================ FILE: web/client/resources/css/themes/dark-bigtext.css ================================================ /* This is a fork of the dark.css theme. The only changes from dark.css are near the very end. */ ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-corner { background: transparent; } ::-webkit-scrollbar-thumb { background-color: rgba(255, 255, 255, .35); border-radius: 10px; } body { color: #D0D0D0; background: fixed #040607; background: fixed -moz-linear-gradient(top, hsl(200,27%,2%) 0%, hsl(203,29%,26%) 100%); background: fixed -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsl(200,27%,2%)), color-stop(100%,hsl(203,29%,26%))); background: fixed -webkit-linear-gradient(top, hsl(200,27%,2%) 0%,hsl(203,29%,26%) 100%); background: fixed -o-linear-gradient(top, hsl(200,27%,2%) 0%,hsl(203,29%,26%) 100%); background: fixed -ms-linear-gradient(top, hsl(200,27%,2%) 0%,hsl(203,29%,26%) 100%); background: fixed linear-gradient(to bottom, hsl(200,27%,2%) 0%,hsl(203,29%,26%) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#040607', endColorstr='#2f4756',GradientType=0 ); } a, .toggle-all-pkg { color: #247D9E; } a:hover, .toggle-all-pkg:hover { color: #33B5E5; } input[type=text] { border-bottom-color: #33B5E5; color: #BBB; } ::-webkit-input-placeholder { color: #555; } :-moz-placeholder { color: #555; } ::-moz-placeholder { color: #555; } :-ms-input-placeholder { color: #555; } .overall { /* Using box-shadow here is not very performant but allows us to animate the change of the background color much more easily. This box-shadow is an ALTERNATIVE, not supplement, to using gradients in this case. */ box-shadow: inset 0 150px 100px -110px rgba(0, 0, 0, .5); } .overall.ok { background: #688E00; } .overall.fail { background: #DB8700; } .overall.panic { background: #A80000; } .overall.buildfail { background: #A4A8AA; } .overall .status { color: #EEE; } .server-down { background: rgba(255, 45, 45, 0.55); color: #FFF; } .toggler { background: #132535; } .toggler:hover { background: #1C374F; } .controls { border-bottom: 1px solid #33B5E5; } .controls li { color: #2A5A84; } .controls li:hover { background: #132535; color: #33B5E5; } .sel { background: #33B5E5 !important; color: #FFF !important; } .pkg-cover-name { text-shadow: 1px 1px 0px #000; } .pkg-cover-name b, .story-pkg-name b { color: #FFF; font-weight: bold; } .pkg-cover:hover, .pkg-cover:hover b { color: #FFF; } .expandable { border-top-color: #33B5E5; } .expandable { background: rgba(0, 0, 0, .2); } .history .item.ok { background: #3f5400; background: -moz-linear-gradient(top, hsl(75,100%,16%) 0%, hsl(76,100%,28%) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsl(75,100%,16%)), color-stop(100%,hsl(76,100%,28%))); background: -webkit-linear-gradient(top, hsl(75,100%,16%) 0%,hsl(76,100%,28%) 100%); background: -o-linear-gradient(top, hsl(75,100%,16%) 0%,hsl(76,100%,28%) 100%); background: -ms-linear-gradient(top, hsl(75,100%,16%) 0%,hsl(76,100%,28%) 100%); background: linear-gradient(to bottom, hsl(75,100%,16%) 0%,hsl(76,100%,28%) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3f5400', endColorstr='#698f00',GradientType=0 ); } .history .item.fail { background: #7f4e00; background: -moz-linear-gradient(top, hsl(37,100%,25%) 0%, hsl(37,100%,43%) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsl(37,100%,25%)), color-stop(100%,hsl(37,100%,43%))); background: -webkit-linear-gradient(top, hsl(37,100%,25%) 0%,hsl(37,100%,43%) 100%); background: -o-linear-gradient(top, hsl(37,100%,25%) 0%,hsl(37,100%,43%) 100%); background: -ms-linear-gradient(top, hsl(37,100%,25%) 0%,hsl(37,100%,43%) 100%); background: linear-gradient(to bottom, hsl(37,100%,25%) 0%,hsl(37,100%,43%) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#7f4e00', endColorstr='#db8700',GradientType=0 ); } .history .item.panic { background: #660000; background: -moz-linear-gradient(top, hsl(0,100%,20%) 0%, hsl(0,100%,33%) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsl(0,100%,20%)), color-stop(100%,hsl(0,100%,33%))); background: -webkit-linear-gradient(top, hsl(0,100%,20%) 0%,hsl(0,100%,33%) 100%); background: -o-linear-gradient(top, hsl(0,100%,20%) 0%,hsl(0,100%,33%) 100%); background: -ms-linear-gradient(top, hsl(0,100%,20%) 0%,hsl(0,100%,33%) 100%); background: linear-gradient(to bottom, hsl(0,100%,20%) 0%,hsl(0,100%,33%) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#660000', endColorstr='#a80000',GradientType=0 ); } .history .item.buildfail { background: #282f33; background: -moz-linear-gradient(top, hsl(202,12%,18%) 0%, hsl(208,5%,48%) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsl(202,12%,18%)), color-stop(100%,hsl(208,5%,48%))); background: -webkit-linear-gradient(top, hsl(202,12%,18%) 0%,hsl(208,5%,48%) 100%); background: -o-linear-gradient(top, hsl(202,12%,18%) 0%,hsl(208,5%,48%) 100%); background: -ms-linear-gradient(top, hsl(202,12%,18%) 0%,hsl(208,5%,48%) 100%); background: linear-gradient(to bottom, hsl(202,12%,18%) 0%,hsl(208,5%,48%) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#282f33', endColorstr='#757c82',GradientType=0 ); } .enum { border-color: #2B597F; } .enum > li { border-left-color: #2B597F; } .enum > li:hover { background: rgba(55, 114, 163, .25); } .group { background: -moz-linear-gradient(top, rgba(16,59,71,0) 0%, rgba(16,59,71,1) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(16,59,71,0)), color-stop(100%,rgba(16,59,71,1))); background: -webkit-linear-gradient(top, rgba(16,59,71,0) 0%,rgba(16,59,71,1) 100%); background: -o-linear-gradient(top, rgba(16,59,71,0) 0%,rgba(16,59,71,1) 100%); background: -ms-linear-gradient(top, rgba(16,59,71,0) 0%,rgba(16,59,71,1) 100%); background: linear-gradient(to top, rgba(16,59,71,0) 0%,rgba(16,59,71,1) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00103b47', endColorstr='#103b47',GradientType=0 ); } .stats { color: #FFF; } .error { color: #F58888 !important; background: rgba(255, 45, 45, 0.35) !important; } .spin-slowly, .spin-once { color: #33B5E5 !important; } .frame .col, footer { border-color: #33B5E5; } footer { background: rgba(0, 0, 0, .5); } footer .recording .fa { color: #CC0000; } footer .replay .fa { color: #33B5E5; } footer .paused .fa { color: #AAA; } footer .recording.replay .fa { color: #33B5E5; } .buildfail-pkg { background: rgba(255, 255, 255, .1); } .buildfail-output { background: rgba(255, 255, 255, .2); } .panic-pkg { background: rgba(255, 0, 0, .3); } .panic-story { padding: 10px; background: rgba(255, 0, 0, .1); } .panic-story a, .panic-summary { color: #E94A4A; } .panic-output { color: #FF8181; } .failure-pkg { background: rgba(255, 153, 0, .42); } .failure-story { padding: 10px; background: rgba(255, 153, 0, .1); } .failure-story a { color: #FFB518; } .failure-output { color: #FFBD47; } .failure-file { color: #FFF; } .diffviewer td { border-color: rgba(0, 0, 0, .3); } /* prettyTextDiff expected/deleted colors */ .diffviewer .exp, .diff del { background: rgba(131, 252, 131, 0.22); } /* prettyTextDiff actual/inserted colors */ .diffviewer .act, .diff ins { background: rgba(255, 52, 52, 0.33); } .story-links a, .test-name-link a { color: inherit; } .story-pkg { background: rgba(0, 0, 0, .4); } .story-pkg:hover { background: rgba(255, 255, 255, .05); } .story-line + .story-line { border-top: 1px dashed rgba(255, 255, 255, .08); } .story-line-desc .message { color: #999; } .story-line-summary-container { border-right: 1px dashed #333; } .story-line.ok .story-line-status { background: #008000; } .story-line.ok:hover, .story-line.ok.story-line-sel { background: rgba(0, 128, 0, .1); } .story-line.fail .story-line-status { background: #EA9C4D; } .story-line.fail:hover, .story-line.fail.story-line-sel { background: rgba(234, 156, 77, .1); } .story-line.panic .story-line-status { background: #FF3232; } .story-line.panic:hover, .story-line.panic.story-line-sel { background: rgba(255, 50, 50, .1); } .story-line.skip .story-line-status { background: #AAA; } .story-line.skip:hover, .story-line.skip.story-line-sel { background: rgba(255, 255, 255, .1); } .statusicon.ok { color: #76C13C; } .statusicon.fail, .fail-clr { color: #EA9C4D; } .statusicon.panic, .statusicon.panic .fa, .panic-clr { color: #FF3232; } .statusicon.skip, .skip-clr { color: #888; } .log .timestamp { color: #999; } .clr-red { color: #FF2222; } .tipsy-inner { background-color: #FAFAFA; color: #222; } .tipsy-arrow { border: 8px dashed #FAFAFA; } .tipsy-arrow-n, .tipsy-arrow-s, .tipsy-arrow-e, .tipsy-arrow-w, { border-color: #FAFAFA; } /***************************************************************/ /*************************** Tweaks ****************************/ /***************************************************************/ /* More space for stories */ div#col-3 { display: none; } /* hides the log */ div#col-2 { width: 85%; } /* fill it in with stories */ /* Bigger Text */ .story-line { font-size: 16px; } .story-line b { font-size: 20px; } td.story-pkg-name { font-size: 24px; } /* Smaller Header */ div.overall { padding: 10px 0 0px; } .overall .status { font-size: 36px; } /***************************************************************/ ================================================ FILE: web/client/resources/css/themes/dark.css ================================================ ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-corner { background: transparent; } ::-webkit-scrollbar-thumb { background-color: rgba(255, 255, 255, .35); border-radius: 10px; } body { color: #D0D0D0; background: fixed #040607; background: fixed -moz-linear-gradient(top, hsl(200,27%,2%) 0%, hsl(203,29%,26%) 100%); background: fixed -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsl(200,27%,2%)), color-stop(100%,hsl(203,29%,26%))); background: fixed -webkit-linear-gradient(top, hsl(200,27%,2%) 0%,hsl(203,29%,26%) 100%); background: fixed -o-linear-gradient(top, hsl(200,27%,2%) 0%,hsl(203,29%,26%) 100%); background: fixed -ms-linear-gradient(top, hsl(200,27%,2%) 0%,hsl(203,29%,26%) 100%); background: fixed linear-gradient(to bottom, hsl(200,27%,2%) 0%,hsl(203,29%,26%) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#040607', endColorstr='#2f4756',GradientType=0 ); } a, .toggle-all-pkg { color: #247D9E; } a:hover, .toggle-all-pkg:hover { color: #33B5E5; } input[type=text] { border-bottom-color: #33B5E5; color: #BBB; } ::-webkit-input-placeholder { color: #555; } :-moz-placeholder { color: #555; } ::-moz-placeholder { color: #555; } :-ms-input-placeholder { color: #555; } .overall { /* Using box-shadow here is not very performant but allows us to animate the change of the background color much more easily. This box-shadow is an ALTERNATIVE, not supplement, to using gradients in this case. */ box-shadow: inset 0 150px 100px -110px rgba(0, 0, 0, .5); } .overall.ok { background: #688E00; } .overall.fail { background: #DB8700; } .overall.panic { background: #A80000; } .overall.buildfail { background: #A4A8AA; } .overall .status { color: #EEE; } .server-down { background: rgba(255, 45, 45, 0.55); color: #FFF; } .toggler { background: #132535; } .toggler:hover { background: #1C374F; } .controls { border-bottom: 1px solid #33B5E5; } .controls li { color: #2A5A84; } .controls li:hover { background: #132535; color: #33B5E5; } .sel { background: #33B5E5 !important; color: #FFF !important; } .pkg-cover-name { text-shadow: 1px 1px 0px #000; } .pkg-cover-name b, .story-pkg-name b { color: #FFF; font-weight: bold; } .pkg-cover:hover, .pkg-cover:hover b { color: #FFF; } .expandable { border-top-color: #33B5E5; } .expandable { background: rgba(0, 0, 0, .2); } .history .item.ok { background: #3f5400; background: -moz-linear-gradient(top, hsl(75,100%,16%) 0%, hsl(76,100%,28%) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsl(75,100%,16%)), color-stop(100%,hsl(76,100%,28%))); background: -webkit-linear-gradient(top, hsl(75,100%,16%) 0%,hsl(76,100%,28%) 100%); background: -o-linear-gradient(top, hsl(75,100%,16%) 0%,hsl(76,100%,28%) 100%); background: -ms-linear-gradient(top, hsl(75,100%,16%) 0%,hsl(76,100%,28%) 100%); background: linear-gradient(to bottom, hsl(75,100%,16%) 0%,hsl(76,100%,28%) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3f5400', endColorstr='#698f00',GradientType=0 ); } .history .item.fail { background: #7f4e00; background: -moz-linear-gradient(top, hsl(37,100%,25%) 0%, hsl(37,100%,43%) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsl(37,100%,25%)), color-stop(100%,hsl(37,100%,43%))); background: -webkit-linear-gradient(top, hsl(37,100%,25%) 0%,hsl(37,100%,43%) 100%); background: -o-linear-gradient(top, hsl(37,100%,25%) 0%,hsl(37,100%,43%) 100%); background: -ms-linear-gradient(top, hsl(37,100%,25%) 0%,hsl(37,100%,43%) 100%); background: linear-gradient(to bottom, hsl(37,100%,25%) 0%,hsl(37,100%,43%) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#7f4e00', endColorstr='#db8700',GradientType=0 ); } .history .item.panic { background: #660000; background: -moz-linear-gradient(top, hsl(0,100%,20%) 0%, hsl(0,100%,33%) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsl(0,100%,20%)), color-stop(100%,hsl(0,100%,33%))); background: -webkit-linear-gradient(top, hsl(0,100%,20%) 0%,hsl(0,100%,33%) 100%); background: -o-linear-gradient(top, hsl(0,100%,20%) 0%,hsl(0,100%,33%) 100%); background: -ms-linear-gradient(top, hsl(0,100%,20%) 0%,hsl(0,100%,33%) 100%); background: linear-gradient(to bottom, hsl(0,100%,20%) 0%,hsl(0,100%,33%) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#660000', endColorstr='#a80000',GradientType=0 ); } .history .item.buildfail { background: #282f33; background: -moz-linear-gradient(top, hsl(202,12%,18%) 0%, hsl(208,5%,48%) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsl(202,12%,18%)), color-stop(100%,hsl(208,5%,48%))); background: -webkit-linear-gradient(top, hsl(202,12%,18%) 0%,hsl(208,5%,48%) 100%); background: -o-linear-gradient(top, hsl(202,12%,18%) 0%,hsl(208,5%,48%) 100%); background: -ms-linear-gradient(top, hsl(202,12%,18%) 0%,hsl(208,5%,48%) 100%); background: linear-gradient(to bottom, hsl(202,12%,18%) 0%,hsl(208,5%,48%) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#282f33', endColorstr='#757c82',GradientType=0 ); } .enum { border-color: #2B597F; } .enum > li { border-left-color: #2B597F; } .enum > li:hover { background: rgba(55, 114, 163, .25); } .group { background: -moz-linear-gradient(top, rgba(16,59,71,0) 0%, rgba(16,59,71,1) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(16,59,71,0)), color-stop(100%,rgba(16,59,71,1))); background: -webkit-linear-gradient(top, rgba(16,59,71,0) 0%,rgba(16,59,71,1) 100%); background: -o-linear-gradient(top, rgba(16,59,71,0) 0%,rgba(16,59,71,1) 100%); background: -ms-linear-gradient(top, rgba(16,59,71,0) 0%,rgba(16,59,71,1) 100%); background: linear-gradient(to top, rgba(16,59,71,0) 0%,rgba(16,59,71,1) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00103b47', endColorstr='#103b47',GradientType=0 ); } .stats { color: #FFF; } .error { color: #F58888 !important; background: rgba(255, 45, 45, 0.35) !important; } .spin-slowly, .spin-once { color: #33B5E5 !important; } .frame .col, footer { border-color: #33B5E5; } footer { background: rgba(0, 0, 0, .5); } footer .recording .fa { color: #CC0000; } footer .replay .fa { color: #33B5E5; } footer .paused .fa { color: #AAA; } footer .recording.replay .fa { color: #33B5E5; } .buildfail-pkg { background: rgba(255, 255, 255, .1); } .buildfail-output { background: rgba(255, 255, 255, .2); } .panic-pkg { background: rgba(255, 0, 0, .3); } .panic-story { padding: 10px; background: rgba(255, 0, 0, .1); } .panic-story a, .panic-summary { color: #E94A4A; } .panic-output { color: #FF8181; } .failure-pkg { background: rgba(255, 153, 0, .42); } .failure-story { padding: 10px; background: rgba(255, 153, 0, .1); } .failure-story a { color: #FFB518; } .failure-output { color: #FFBD47; } .failure-file { color: #FFF; } .diffviewer td { border-color: rgba(0, 0, 0, .3); } /* prettyTextDiff expected/deleted colors */ .diffviewer .exp, .diff del { background: rgba(131, 252, 131, 0.22); } /* prettyTextDiff actual/inserted colors */ .diffviewer .act, .diff ins { background: rgba(255, 52, 52, 0.33); } .story-links a, .test-name-link a { color: inherit; } .story-pkg { background: rgba(0, 0, 0, .4); } .story-pkg:hover { background: rgba(255, 255, 255, .05); } .story-line + .story-line { border-top: 1px dashed rgba(255, 255, 255, .08); } .story-line-desc .message { color: #999; } .story-line-summary-container { border-right: 1px dashed #333; } .story-line.ok .story-line-status { background: #008000; } .story-line.ok:hover, .story-line.ok.story-line-sel { background: rgba(0, 128, 0, .1); } .story-line.fail .story-line-status { background: #EA9C4D; } .story-line.fail:hover, .story-line.fail.story-line-sel { background: rgba(234, 156, 77, .1); } .story-line.panic .story-line-status { background: #FF3232; } .story-line.panic:hover, .story-line.panic.story-line-sel { background: rgba(255, 50, 50, .1); } .story-line.skip .story-line-status { background: #AAA; } .story-line.skip:hover, .story-line.skip.story-line-sel { background: rgba(255, 255, 255, .1); } .statusicon.ok { color: #76C13C; } .statusicon.fail, .fail-clr { color: #EA9C4D; } .statusicon.panic, .statusicon.panic .fa, .panic-clr { color: #FF3232; } .statusicon.skip, .skip-clr { color: #888; } .ansi-green { color: #76C13C; } .ansi-yellow { color: #EA9C4D; } .ansi-red { color: #FF3232; } .ansi-black { color: #000000; } .ansi-blue { color: #FF3232; } .ansi-purple { color: #C646C6; } .ansi-cyan { color: #00CDCD; } .ansi-white { color: #FFFFFF; } .log .timestamp { color: #999; } .clr-red { color: #FF2222; } .tipsy-inner { background-color: #FAFAFA; color: #222; } .tipsy-arrow { border: 8px dashed #FAFAFA; } .tipsy-arrow-n, .tipsy-arrow-s, .tipsy-arrow-e, .tipsy-arrow-w, { border-color: #FAFAFA; } ================================================ FILE: web/client/resources/css/themes/light.css ================================================ ::-webkit-scrollbar-thumb { background-color: rgba(0, 0, 0, .35); border-radius: 10px; } ::-webkit-input-placeholder { color: #CCC; } :-moz-placeholder { color: #CCC; } ::-moz-placeholder { color: #CCC; } :-ms-input-placeholder { color: #CCC; } body { color: #444; background: #F4F4F4; } a { color: #247D9E; } a:hover { color: #33B5E5; } .overall.ok, .history .item.ok { background: #8CB700; /* Can't decide: #5AA02C */ } .overall.fail, .history .item.fail { background: #E79C07; } .overall.panic, .history .item.panic { background: #BB0000; } .overall.buildfail, .history .item.buildfail { background: #828c95; } .overall .status { color: #EEE; } .server-down { background: #BB0000; color: #FFF; } .toggler { background: #6887A3; color: #FFF; } .toggler:hover { background: #465B6D; } .toggler .fa { color: #FFF; } #logo { color: #6887A3; } .controls { border-bottom: 1px solid #33B5E5; } li.fa, a.fa, .toggle-all-pkg { color: #6887A3; } li.fa:hover, a.fa:hover, .toggle-all-pkg:hover { color: #465B6D; } li.fa:active, a.fa:active, .toggle-all-pkg:active { color: #33B5E5; } .controls li, .enum > li { border-left-color: #33B5E5; } .controls li:hover, .enum > li:hover { background: #CFE6F9; } .enum { border-color: #33B5E5; } .sel { background: #33B5E5 !important; color: #FFF !important; } .pkg-cover-name b, .story-pkg-name b { color: #000; font-weight: bold; } .expandable { background: rgba(0, 0, 0, .1); border-top-color: #33B5E5; } .history .item { color: #FFF; } .spin-slowly, .spin-once { color: #33B5E5 !important; } input[type=text] { border-bottom-color: #33B5E5; color: #333; } .error { color: #CC0000 !important; background: #FFD2D2 !important; } footer { background: #F4F4F4; } .frame .col, footer { border-color: #33B5E5; } footer .recording .fa { color: #CC0000; } footer .replay .fa { color: #33B5E5; } footer .paused .fa { color: #333; } .buildfail-pkg { background: #CCC; } .buildfail-output { background: #EEE; } .panic-pkg { background: #E94D4D; color: #FFF; } .panics .panic-details { border: 5px solid #E94D4D; border-top: 0; border-bottom: 0; } .panic-details { color: #CC0000; } .panics .panic:last-child .panic-details { border-bottom: 5px solid #E94D4D; } .panic-story { padding: 10px; } .panics .panic-output { background: #FFF; } .failure-pkg { background: #FFA300; color: #FFF; } .failures .failure-details { border: 5px solid #FFA300; border-top: 0; border-bottom: 0; } .failures .failure:last-child .failure-details { border-bottom: 5px solid #FFA300; } .failure-story { padding: 10px; color: #A87A00; } .stories .failure-output { color: #EA9C4D; } .failures .failure-output { background: #FFF; } .failure-file { color: #000; } .diffviewer td { border-color: #CCC; background: #FFF; } /* prettyTextDiff expected/deleted colors */ .diffviewer .exp, .diff del { background: #ADFFAD; } /* prettyTextDiff actual/inserted colors */ .diffviewer .act, .diff ins { background: #FFC0C0; } .story-links a, .test-name-link a { color: inherit; } .story-pkg { background: #E8E8E8; } .story-pkg:hover { background: #DFDFDF; } .story-line { background: #FFF; } .story-line-desc .message { color: #888; } .story-line + .story-line { border-top: 1px dashed #DDD; } .story-line-summary-container { border-right: 1px dashed #DDD; } .story-line.ok .story-line-status { background: #8CB700; } .story-line.ok:hover, .story-line.ok.story-line-sel { background: #F4FFD8; } .story-line.fail .story-line-status { background: #E79C07; } .story-line.fail:hover, .story-line.fail.story-line-sel { background: #FFF1DB; } .story-line.panic .story-line-status { background: #DD0606; } .story-line.panic:hover, .story-line.panic.story-line-sel { background: #FFE8E8; } .story-line.skip .story-line-status { background: #4E4E4E; } .story-line.skip:hover, .story-line.skip.story-line-sel { background: #F2F2F2; } .statusicon.ok { color: #76C13C; } .statusicon.fail, .fail-clr { color: #EA9C4D; } .statusicon.panic, .statusicon.panic .fa, .panic-clr { color: #FF3232; } .statusicon.skip, .skip-clr { color: #AAA; } .ansi-green { color: #76C13C; } .ansi-yellow { color: #EA9C4D; } .log .timestamp { color: #999; } .clr-red, a.clr-red { color: #CC0000; } .tipsy-inner { background-color: #000; color: #FFF; } .tipsy-arrow { border: 8px dashed #000; } .tipsy-arrow-n, .tipsy-arrow-s, .tipsy-arrow-e, .tipsy-arrow-w, { border-color: #000; } ================================================ FILE: web/client/resources/css/tipsy.css ================================================ .tipsy { font-size: 12px; position: absolute; padding: 8px; z-index: 100000; font-family: 'Open Sans'; line-height: 1.25em; } .tipsy-inner { max-width: 200px; padding: 5px 7px; text-align: center; } /* Rounded corners */ /*.tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; }*/ /* Shadow */ /*.tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; }*/ .tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; } .tipsy-n .tipsy-arrow, .tipsy-nw .tipsy-arrow, .tipsy-ne .tipsy-arrow { border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; } .tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -7px; } .tipsy-nw .tipsy-arrow { top: 0; left: 10px; } .tipsy-ne .tipsy-arrow { top: 0; right: 10px; } .tipsy-s .tipsy-arrow, .tipsy-sw .tipsy-arrow, .tipsy-se .tipsy-arrow { border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -7px; } .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; } .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; } .tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -7px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; } .tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -7px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; } ================================================ FILE: web/client/resources/fonts/FontAwesome/README.md ================================================ #[Font Awesome v4.5.0](http://fontawesome.io) ###The iconic font and CSS framework Font Awesome is a full suite of 605 pictographic icons for easy scalable vector graphics on websites, created and maintained by [Dave Gandy](http://twitter.com/davegandy). Stay up to date with the latest release and announcements on Twitter: [@fontawesome](http://twitter.com/fontawesome). Get started at http://fontawesome.io! ##License - The Font Awesome font is licensed under the SIL OFL 1.1: - http://scripts.sil.org/OFL - Font Awesome CSS, LESS, and Sass files are licensed under the MIT License: - http://opensource.org/licenses/mit-license.html - The Font Awesome documentation is licensed under the CC BY 3.0 License: - http://creativecommons.org/licenses/by/3.0/ - Attribution is no longer required as of Font Awesome 3.0, but much appreciated: - `Font Awesome by Dave Gandy - http://fontawesome.io` - Full details: http://fontawesome.io/license ##Changelog - [v4.5.0 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?q=milestone%3A4.5.0+is%3Aclosed) - [v4.4.0 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?q=milestone%3A4.4.0+is%3Aclosed) - [v4.3.0 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?q=milestone%3A4.3.0+is%3Aclosed) - [v4.2.0 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?milestone=12&page=1&state=closed) - [v4.1.0 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?milestone=6&page=1&state=closed) - [v4.0.3 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?milestone=9&page=1&state=closed) - [v4.0.2 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?milestone=8&page=1&state=closed) - [v4.0.1 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?milestone=7&page=1&state=closed) - [v4.0.0 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?milestone=2&page=1&state=closed) - [v3.2.1 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?milestone=5&page=1&state=closed) - [v3.2.0 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?milestone=3&page=1&state=closed) - [v3.1.1 GitHub milestones](https://github.com/FortAwesome/Font-Awesome/issues?milestone=4&page=1&state=closed) - v3.1.0 - Added 54 icons, icon stacking styles, flipping and rotating icons, removed Sass support - v3.0.2 - much improved rendering and alignment in IE7 - v3.0.1 - much improved rendering in webkit, various bug fixes - v3.0.0 - all icons redesigned from scratch, optimized for Bootstrap's 14px default ## Contributing Please read through our [contributing guidelines](https://github.com/FortAwesome/Font-Awesome/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development. ##Versioning Font Awesome will be maintained under the Semantic Versioning guidelines as much as possible. Releases will be numbered with the following format: `..` And constructed with the following guidelines: * Breaking backward compatibility bumps the major (and resets the minor and patch) * New additions, including new icons, without breaking backward compatibility bumps the minor (and resets the patch) * Bug fixes and misc changes bumps the patch For more information on SemVer, please visit http://semver.org. ##Author - Email: dave@fontawesome.io - Twitter: http://twitter.com/davegandy - GitHub: https://github.com/davegandy ##Component To include as a [component](http://github.com/component/component), just run $ component install FortAwesome/Font-Awesome Or add "FortAwesome/Font-Awesome": "*" to the `dependencies` in your `component.json`. ## Hacking on Font Awesome **Before you can build the project**, you must first have the following installed: - [Ruby](https://www.ruby-lang.org/en/) - Ruby Development Headers - **Ubuntu:** `sudo apt-get install ruby-dev` *(Only if you're __NOT__ using `rbenv` or `rvm`)* - **Windows:** [DevKit](http://rubyinstaller.org/) - [Bundler](http://bundler.io/) (Run `gem install bundler` to install). - [Node Package Manager (AKA NPM)](https://docs.npmjs.com/getting-started/installing-node) - [Less](http://lesscss.org/) (Run `npm install -g less` to install). - [Less Plugin: Clean CSS](https://github.com/less/less-plugin-clean-css) (Run `npm install -g less-plugin-clean-css` to install). From the root of the repository, install the tools used to develop. $ bundle install $ npm install Build the project and documentation: $ bundle exec jekyll build Or serve it on a local server on http://localhost:7998/Font-Awesome/: $ bundle exec jekyll -w serve ================================================ FILE: web/client/resources/fonts/FontAwesome/css/font-awesome.css ================================================ /*! * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ /* FONT PATH * -------------------------- */ @font-face { font-family: 'FontAwesome'; src: url('../fonts/fontawesome-webfont.eot?v=4.5.0'); src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg'); font-weight: normal; font-style: normal; } .fa { display: inline-block; font: normal normal normal 14px/1 FontAwesome; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* makes the font 33% larger relative to the icon container */ .fa-lg { font-size: 1.33333333em; line-height: 0.75em; vertical-align: -15%; } .fa-2x { font-size: 2em; } .fa-3x { font-size: 3em; } .fa-4x { font-size: 4em; } .fa-5x { font-size: 5em; } .fa-fw { width: 1.28571429em; text-align: center; } .fa-ul { padding-left: 0; margin-left: 2.14285714em; list-style-type: none; } .fa-ul > li { position: relative; } .fa-li { position: absolute; left: -2.14285714em; width: 2.14285714em; top: 0.14285714em; text-align: center; } .fa-li.fa-lg { left: -1.85714286em; } .fa-border { padding: .2em .25em .15em; border: solid 0.08em #eeeeee; border-radius: .1em; } .fa-pull-left { float: left; } .fa-pull-right { float: right; } .fa.fa-pull-left { margin-right: .3em; } .fa.fa-pull-right { margin-left: .3em; } /* Deprecated as of 4.4.0 */ .pull-right { float: right; } .pull-left { float: left; } .fa.pull-left { margin-right: .3em; } .fa.pull-right { margin-left: .3em; } .fa-spin { -webkit-animation: fa-spin 2s infinite linear; animation: fa-spin 2s infinite linear; } .fa-pulse { -webkit-animation: fa-spin 1s infinite steps(8); animation: fa-spin 1s infinite steps(8); } @-webkit-keyframes fa-spin { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); transform: rotate(359deg); } } @keyframes fa-spin { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); transform: rotate(359deg); } } .fa-rotate-90 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); } .fa-rotate-180 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); transform: rotate(180deg); } .fa-rotate-270 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); -webkit-transform: rotate(270deg); -ms-transform: rotate(270deg); transform: rotate(270deg); } .fa-flip-horizontal { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); -webkit-transform: scale(-1, 1); -ms-transform: scale(-1, 1); transform: scale(-1, 1); } .fa-flip-vertical { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); -webkit-transform: scale(1, -1); -ms-transform: scale(1, -1); transform: scale(1, -1); } :root .fa-rotate-90, :root .fa-rotate-180, :root .fa-rotate-270, :root .fa-flip-horizontal, :root .fa-flip-vertical { filter: none; } .fa-stack { position: relative; display: inline-block; width: 2em; height: 2em; line-height: 2em; vertical-align: middle; } .fa-stack-1x, .fa-stack-2x { position: absolute; left: 0; width: 100%; text-align: center; } .fa-stack-1x { line-height: inherit; } .fa-stack-2x { font-size: 2em; } .fa-inverse { color: #ffffff; } /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons */ .fa-glass:before { content: "\f000"; } .fa-music:before { content: "\f001"; } .fa-search:before { content: "\f002"; } .fa-envelope-o:before { content: "\f003"; } .fa-heart:before { content: "\f004"; } .fa-star:before { content: "\f005"; } .fa-star-o:before { content: "\f006"; } .fa-user:before { content: "\f007"; } .fa-film:before { content: "\f008"; } .fa-th-large:before { content: "\f009"; } .fa-th:before { content: "\f00a"; } .fa-th-list:before { content: "\f00b"; } .fa-check:before { content: "\f00c"; } .fa-remove:before, .fa-close:before, .fa-times:before { content: "\f00d"; } .fa-search-plus:before { content: "\f00e"; } .fa-search-minus:before { content: "\f010"; } .fa-power-off:before { content: "\f011"; } .fa-signal:before { content: "\f012"; } .fa-gear:before, .fa-cog:before { content: "\f013"; } .fa-trash-o:before { content: "\f014"; } .fa-home:before { content: "\f015"; } .fa-file-o:before { content: "\f016"; } .fa-clock-o:before { content: "\f017"; } .fa-road:before { content: "\f018"; } .fa-download:before { content: "\f019"; } .fa-arrow-circle-o-down:before { content: "\f01a"; } .fa-arrow-circle-o-up:before { content: "\f01b"; } .fa-inbox:before { content: "\f01c"; } .fa-play-circle-o:before { content: "\f01d"; } .fa-rotate-right:before, .fa-repeat:before { content: "\f01e"; } .fa-refresh:before { content: "\f021"; } .fa-list-alt:before { content: "\f022"; } .fa-lock:before { content: "\f023"; } .fa-flag:before { content: "\f024"; } .fa-headphones:before { content: "\f025"; } .fa-volume-off:before { content: "\f026"; } .fa-volume-down:before { content: "\f027"; } .fa-volume-up:before { content: "\f028"; } .fa-qrcode:before { content: "\f029"; } .fa-barcode:before { content: "\f02a"; } .fa-tag:before { content: "\f02b"; } .fa-tags:before { content: "\f02c"; } .fa-book:before { content: "\f02d"; } .fa-bookmark:before { content: "\f02e"; } .fa-print:before { content: "\f02f"; } .fa-camera:before { content: "\f030"; } .fa-font:before { content: "\f031"; } .fa-bold:before { content: "\f032"; } .fa-italic:before { content: "\f033"; } .fa-text-height:before { content: "\f034"; } .fa-text-width:before { content: "\f035"; } .fa-align-left:before { content: "\f036"; } .fa-align-center:before { content: "\f037"; } .fa-align-right:before { content: "\f038"; } .fa-align-justify:before { content: "\f039"; } .fa-list:before { content: "\f03a"; } .fa-dedent:before, .fa-outdent:before { content: "\f03b"; } .fa-indent:before { content: "\f03c"; } .fa-video-camera:before { content: "\f03d"; } .fa-photo:before, .fa-image:before, .fa-picture-o:before { content: "\f03e"; } .fa-pencil:before { content: "\f040"; } .fa-map-marker:before { content: "\f041"; } .fa-adjust:before { content: "\f042"; } .fa-tint:before { content: "\f043"; } .fa-edit:before, .fa-pencil-square-o:before { content: "\f044"; } .fa-share-square-o:before { content: "\f045"; } .fa-check-square-o:before { content: "\f046"; } .fa-arrows:before { content: "\f047"; } .fa-step-backward:before { content: "\f048"; } .fa-fast-backward:before { content: "\f049"; } .fa-backward:before { content: "\f04a"; } .fa-play:before { content: "\f04b"; } .fa-pause:before { content: "\f04c"; } .fa-stop:before { content: "\f04d"; } .fa-forward:before { content: "\f04e"; } .fa-fast-forward:before { content: "\f050"; } .fa-step-forward:before { content: "\f051"; } .fa-eject:before { content: "\f052"; } .fa-chevron-left:before { content: "\f053"; } .fa-chevron-right:before { content: "\f054"; } .fa-plus-circle:before { content: "\f055"; } .fa-minus-circle:before { content: "\f056"; } .fa-times-circle:before { content: "\f057"; } .fa-check-circle:before { content: "\f058"; } .fa-question-circle:before { content: "\f059"; } .fa-info-circle:before { content: "\f05a"; } .fa-crosshairs:before { content: "\f05b"; } .fa-times-circle-o:before { content: "\f05c"; } .fa-check-circle-o:before { content: "\f05d"; } .fa-ban:before { content: "\f05e"; } .fa-arrow-left:before { content: "\f060"; } .fa-arrow-right:before { content: "\f061"; } .fa-arrow-up:before { content: "\f062"; } .fa-arrow-down:before { content: "\f063"; } .fa-mail-forward:before, .fa-share:before { content: "\f064"; } .fa-expand:before { content: "\f065"; } .fa-compress:before { content: "\f066"; } .fa-plus:before { content: "\f067"; } .fa-minus:before { content: "\f068"; } .fa-asterisk:before { content: "\f069"; } .fa-exclamation-circle:before { content: "\f06a"; } .fa-gift:before { content: "\f06b"; } .fa-leaf:before { content: "\f06c"; } .fa-fire:before { content: "\f06d"; } .fa-eye:before { content: "\f06e"; } .fa-eye-slash:before { content: "\f070"; } .fa-warning:before, .fa-exclamation-triangle:before { content: "\f071"; } .fa-plane:before { content: "\f072"; } .fa-calendar:before { content: "\f073"; } .fa-random:before { content: "\f074"; } .fa-comment:before { content: "\f075"; } .fa-magnet:before { content: "\f076"; } .fa-chevron-up:before { content: "\f077"; } .fa-chevron-down:before { content: "\f078"; } .fa-retweet:before { content: "\f079"; } .fa-shopping-cart:before { content: "\f07a"; } .fa-folder:before { content: "\f07b"; } .fa-folder-open:before { content: "\f07c"; } .fa-arrows-v:before { content: "\f07d"; } .fa-arrows-h:before { content: "\f07e"; } .fa-bar-chart-o:before, .fa-bar-chart:before { content: "\f080"; } .fa-twitter-square:before { content: "\f081"; } .fa-facebook-square:before { content: "\f082"; } .fa-camera-retro:before { content: "\f083"; } .fa-key:before { content: "\f084"; } .fa-gears:before, .fa-cogs:before { content: "\f085"; } .fa-comments:before { content: "\f086"; } .fa-thumbs-o-up:before { content: "\f087"; } .fa-thumbs-o-down:before { content: "\f088"; } .fa-star-half:before { content: "\f089"; } .fa-heart-o:before { content: "\f08a"; } .fa-sign-out:before { content: "\f08b"; } .fa-linkedin-square:before { content: "\f08c"; } .fa-thumb-tack:before { content: "\f08d"; } .fa-external-link:before { content: "\f08e"; } .fa-sign-in:before { content: "\f090"; } .fa-trophy:before { content: "\f091"; } .fa-github-square:before { content: "\f092"; } .fa-upload:before { content: "\f093"; } .fa-lemon-o:before { content: "\f094"; } .fa-phone:before { content: "\f095"; } .fa-square-o:before { content: "\f096"; } .fa-bookmark-o:before { content: "\f097"; } .fa-phone-square:before { content: "\f098"; } .fa-twitter:before { content: "\f099"; } .fa-facebook-f:before, .fa-facebook:before { content: "\f09a"; } .fa-github:before { content: "\f09b"; } .fa-unlock:before { content: "\f09c"; } .fa-credit-card:before { content: "\f09d"; } .fa-feed:before, .fa-rss:before { content: "\f09e"; } .fa-hdd-o:before { content: "\f0a0"; } .fa-bullhorn:before { content: "\f0a1"; } .fa-bell:before { content: "\f0f3"; } .fa-certificate:before { content: "\f0a3"; } .fa-hand-o-right:before { content: "\f0a4"; } .fa-hand-o-left:before { content: "\f0a5"; } .fa-hand-o-up:before { content: "\f0a6"; } .fa-hand-o-down:before { content: "\f0a7"; } .fa-arrow-circle-left:before { content: "\f0a8"; } .fa-arrow-circle-right:before { content: "\f0a9"; } .fa-arrow-circle-up:before { content: "\f0aa"; } .fa-arrow-circle-down:before { content: "\f0ab"; } .fa-globe:before { content: "\f0ac"; } .fa-wrench:before { content: "\f0ad"; } .fa-tasks:before { content: "\f0ae"; } .fa-filter:before { content: "\f0b0"; } .fa-briefcase:before { content: "\f0b1"; } .fa-arrows-alt:before { content: "\f0b2"; } .fa-group:before, .fa-users:before { content: "\f0c0"; } .fa-chain:before, .fa-link:before { content: "\f0c1"; } .fa-cloud:before { content: "\f0c2"; } .fa-flask:before { content: "\f0c3"; } .fa-cut:before, .fa-scissors:before { content: "\f0c4"; } .fa-copy:before, .fa-files-o:before { content: "\f0c5"; } .fa-paperclip:before { content: "\f0c6"; } .fa-save:before, .fa-floppy-o:before { content: "\f0c7"; } .fa-square:before { content: "\f0c8"; } .fa-navicon:before, .fa-reorder:before, .fa-bars:before { content: "\f0c9"; } .fa-list-ul:before { content: "\f0ca"; } .fa-list-ol:before { content: "\f0cb"; } .fa-strikethrough:before { content: "\f0cc"; } .fa-underline:before { content: "\f0cd"; } .fa-table:before { content: "\f0ce"; } .fa-magic:before { content: "\f0d0"; } .fa-truck:before { content: "\f0d1"; } .fa-pinterest:before { content: "\f0d2"; } .fa-pinterest-square:before { content: "\f0d3"; } .fa-google-plus-square:before { content: "\f0d4"; } .fa-google-plus:before { content: "\f0d5"; } .fa-money:before { content: "\f0d6"; } .fa-caret-down:before { content: "\f0d7"; } .fa-caret-up:before { content: "\f0d8"; } .fa-caret-left:before { content: "\f0d9"; } .fa-caret-right:before { content: "\f0da"; } .fa-columns:before { content: "\f0db"; } .fa-unsorted:before, .fa-sort:before { content: "\f0dc"; } .fa-sort-down:before, .fa-sort-desc:before { content: "\f0dd"; } .fa-sort-up:before, .fa-sort-asc:before { content: "\f0de"; } .fa-envelope:before { content: "\f0e0"; } .fa-linkedin:before { content: "\f0e1"; } .fa-rotate-left:before, .fa-undo:before { content: "\f0e2"; } .fa-legal:before, .fa-gavel:before { content: "\f0e3"; } .fa-dashboard:before, .fa-tachometer:before { content: "\f0e4"; } .fa-comment-o:before { content: "\f0e5"; } .fa-comments-o:before { content: "\f0e6"; } .fa-flash:before, .fa-bolt:before { content: "\f0e7"; } .fa-sitemap:before { content: "\f0e8"; } .fa-umbrella:before { content: "\f0e9"; } .fa-paste:before, .fa-clipboard:before { content: "\f0ea"; } .fa-lightbulb-o:before { content: "\f0eb"; } .fa-exchange:before { content: "\f0ec"; } .fa-cloud-download:before { content: "\f0ed"; } .fa-cloud-upload:before { content: "\f0ee"; } .fa-user-md:before { content: "\f0f0"; } .fa-stethoscope:before { content: "\f0f1"; } .fa-suitcase:before { content: "\f0f2"; } .fa-bell-o:before { content: "\f0a2"; } .fa-coffee:before { content: "\f0f4"; } .fa-cutlery:before { content: "\f0f5"; } .fa-file-text-o:before { content: "\f0f6"; } .fa-building-o:before { content: "\f0f7"; } .fa-hospital-o:before { content: "\f0f8"; } .fa-ambulance:before { content: "\f0f9"; } .fa-medkit:before { content: "\f0fa"; } .fa-fighter-jet:before { content: "\f0fb"; } .fa-beer:before { content: "\f0fc"; } .fa-h-square:before { content: "\f0fd"; } .fa-plus-square:before { content: "\f0fe"; } .fa-angle-double-left:before { content: "\f100"; } .fa-angle-double-right:before { content: "\f101"; } .fa-angle-double-up:before { content: "\f102"; } .fa-angle-double-down:before { content: "\f103"; } .fa-angle-left:before { content: "\f104"; } .fa-angle-right:before { content: "\f105"; } .fa-angle-up:before { content: "\f106"; } .fa-angle-down:before { content: "\f107"; } .fa-desktop:before { content: "\f108"; } .fa-laptop:before { content: "\f109"; } .fa-tablet:before { content: "\f10a"; } .fa-mobile-phone:before, .fa-mobile:before { content: "\f10b"; } .fa-circle-o:before { content: "\f10c"; } .fa-quote-left:before { content: "\f10d"; } .fa-quote-right:before { content: "\f10e"; } .fa-spinner:before { content: "\f110"; } .fa-circle:before { content: "\f111"; } .fa-mail-reply:before, .fa-reply:before { content: "\f112"; } .fa-github-alt:before { content: "\f113"; } .fa-folder-o:before { content: "\f114"; } .fa-folder-open-o:before { content: "\f115"; } .fa-smile-o:before { content: "\f118"; } .fa-frown-o:before { content: "\f119"; } .fa-meh-o:before { content: "\f11a"; } .fa-gamepad:before { content: "\f11b"; } .fa-keyboard-o:before { content: "\f11c"; } .fa-flag-o:before { content: "\f11d"; } .fa-flag-checkered:before { content: "\f11e"; } .fa-terminal:before { content: "\f120"; } .fa-code:before { content: "\f121"; } .fa-mail-reply-all:before, .fa-reply-all:before { content: "\f122"; } .fa-star-half-empty:before, .fa-star-half-full:before, .fa-star-half-o:before { content: "\f123"; } .fa-location-arrow:before { content: "\f124"; } .fa-crop:before { content: "\f125"; } .fa-code-fork:before { content: "\f126"; } .fa-unlink:before, .fa-chain-broken:before { content: "\f127"; } .fa-question:before { content: "\f128"; } .fa-info:before { content: "\f129"; } .fa-exclamation:before { content: "\f12a"; } .fa-superscript:before { content: "\f12b"; } .fa-subscript:before { content: "\f12c"; } .fa-eraser:before { content: "\f12d"; } .fa-puzzle-piece:before { content: "\f12e"; } .fa-microphone:before { content: "\f130"; } .fa-microphone-slash:before { content: "\f131"; } .fa-shield:before { content: "\f132"; } .fa-calendar-o:before { content: "\f133"; } .fa-fire-extinguisher:before { content: "\f134"; } .fa-rocket:before { content: "\f135"; } .fa-maxcdn:before { content: "\f136"; } .fa-chevron-circle-left:before { content: "\f137"; } .fa-chevron-circle-right:before { content: "\f138"; } .fa-chevron-circle-up:before { content: "\f139"; } .fa-chevron-circle-down:before { content: "\f13a"; } .fa-html5:before { content: "\f13b"; } .fa-css3:before { content: "\f13c"; } .fa-anchor:before { content: "\f13d"; } .fa-unlock-alt:before { content: "\f13e"; } .fa-bullseye:before { content: "\f140"; } .fa-ellipsis-h:before { content: "\f141"; } .fa-ellipsis-v:before { content: "\f142"; } .fa-rss-square:before { content: "\f143"; } .fa-play-circle:before { content: "\f144"; } .fa-ticket:before { content: "\f145"; } .fa-minus-square:before { content: "\f146"; } .fa-minus-square-o:before { content: "\f147"; } .fa-level-up:before { content: "\f148"; } .fa-level-down:before { content: "\f149"; } .fa-check-square:before { content: "\f14a"; } .fa-pencil-square:before { content: "\f14b"; } .fa-external-link-square:before { content: "\f14c"; } .fa-share-square:before { content: "\f14d"; } .fa-compass:before { content: "\f14e"; } .fa-toggle-down:before, .fa-caret-square-o-down:before { content: "\f150"; } .fa-toggle-up:before, .fa-caret-square-o-up:before { content: "\f151"; } .fa-toggle-right:before, .fa-caret-square-o-right:before { content: "\f152"; } .fa-euro:before, .fa-eur:before { content: "\f153"; } .fa-gbp:before { content: "\f154"; } .fa-dollar:before, .fa-usd:before { content: "\f155"; } .fa-rupee:before, .fa-inr:before { content: "\f156"; } .fa-cny:before, .fa-rmb:before, .fa-yen:before, .fa-jpy:before { content: "\f157"; } .fa-ruble:before, .fa-rouble:before, .fa-rub:before { content: "\f158"; } .fa-won:before, .fa-krw:before { content: "\f159"; } .fa-bitcoin:before, .fa-btc:before { content: "\f15a"; } .fa-file:before { content: "\f15b"; } .fa-file-text:before { content: "\f15c"; } .fa-sort-alpha-asc:before { content: "\f15d"; } .fa-sort-alpha-desc:before { content: "\f15e"; } .fa-sort-amount-asc:before { content: "\f160"; } .fa-sort-amount-desc:before { content: "\f161"; } .fa-sort-numeric-asc:before { content: "\f162"; } .fa-sort-numeric-desc:before { content: "\f163"; } .fa-thumbs-up:before { content: "\f164"; } .fa-thumbs-down:before { content: "\f165"; } .fa-youtube-square:before { content: "\f166"; } .fa-youtube:before { content: "\f167"; } .fa-xing:before { content: "\f168"; } .fa-xing-square:before { content: "\f169"; } .fa-youtube-play:before { content: "\f16a"; } .fa-dropbox:before { content: "\f16b"; } .fa-stack-overflow:before { content: "\f16c"; } .fa-instagram:before { content: "\f16d"; } .fa-flickr:before { content: "\f16e"; } .fa-adn:before { content: "\f170"; } .fa-bitbucket:before { content: "\f171"; } .fa-bitbucket-square:before { content: "\f172"; } .fa-tumblr:before { content: "\f173"; } .fa-tumblr-square:before { content: "\f174"; } .fa-long-arrow-down:before { content: "\f175"; } .fa-long-arrow-up:before { content: "\f176"; } .fa-long-arrow-left:before { content: "\f177"; } .fa-long-arrow-right:before { content: "\f178"; } .fa-apple:before { content: "\f179"; } .fa-windows:before { content: "\f17a"; } .fa-android:before { content: "\f17b"; } .fa-linux:before { content: "\f17c"; } .fa-dribbble:before { content: "\f17d"; } .fa-skype:before { content: "\f17e"; } .fa-foursquare:before { content: "\f180"; } .fa-trello:before { content: "\f181"; } .fa-female:before { content: "\f182"; } .fa-male:before { content: "\f183"; } .fa-gittip:before, .fa-gratipay:before { content: "\f184"; } .fa-sun-o:before { content: "\f185"; } .fa-moon-o:before { content: "\f186"; } .fa-archive:before { content: "\f187"; } .fa-bug:before { content: "\f188"; } .fa-vk:before { content: "\f189"; } .fa-weibo:before { content: "\f18a"; } .fa-renren:before { content: "\f18b"; } .fa-pagelines:before { content: "\f18c"; } .fa-stack-exchange:before { content: "\f18d"; } .fa-arrow-circle-o-right:before { content: "\f18e"; } .fa-arrow-circle-o-left:before { content: "\f190"; } .fa-toggle-left:before, .fa-caret-square-o-left:before { content: "\f191"; } .fa-dot-circle-o:before { content: "\f192"; } .fa-wheelchair:before { content: "\f193"; } .fa-vimeo-square:before { content: "\f194"; } .fa-turkish-lira:before, .fa-try:before { content: "\f195"; } .fa-plus-square-o:before { content: "\f196"; } .fa-space-shuttle:before { content: "\f197"; } .fa-slack:before { content: "\f198"; } .fa-envelope-square:before { content: "\f199"; } .fa-wordpress:before { content: "\f19a"; } .fa-openid:before { content: "\f19b"; } .fa-institution:before, .fa-bank:before, .fa-university:before { content: "\f19c"; } .fa-mortar-board:before, .fa-graduation-cap:before { content: "\f19d"; } .fa-yahoo:before { content: "\f19e"; } .fa-google:before { content: "\f1a0"; } .fa-reddit:before { content: "\f1a1"; } .fa-reddit-square:before { content: "\f1a2"; } .fa-stumbleupon-circle:before { content: "\f1a3"; } .fa-stumbleupon:before { content: "\f1a4"; } .fa-delicious:before { content: "\f1a5"; } .fa-digg:before { content: "\f1a6"; } .fa-pied-piper:before { content: "\f1a7"; } .fa-pied-piper-alt:before { content: "\f1a8"; } .fa-drupal:before { content: "\f1a9"; } .fa-joomla:before { content: "\f1aa"; } .fa-language:before { content: "\f1ab"; } .fa-fax:before { content: "\f1ac"; } .fa-building:before { content: "\f1ad"; } .fa-child:before { content: "\f1ae"; } .fa-paw:before { content: "\f1b0"; } .fa-spoon:before { content: "\f1b1"; } .fa-cube:before { content: "\f1b2"; } .fa-cubes:before { content: "\f1b3"; } .fa-behance:before { content: "\f1b4"; } .fa-behance-square:before { content: "\f1b5"; } .fa-steam:before { content: "\f1b6"; } .fa-steam-square:before { content: "\f1b7"; } .fa-recycle:before { content: "\f1b8"; } .fa-automobile:before, .fa-car:before { content: "\f1b9"; } .fa-cab:before, .fa-taxi:before { content: "\f1ba"; } .fa-tree:before { content: "\f1bb"; } .fa-spotify:before { content: "\f1bc"; } .fa-deviantart:before { content: "\f1bd"; } .fa-soundcloud:before { content: "\f1be"; } .fa-database:before { content: "\f1c0"; } .fa-file-pdf-o:before { content: "\f1c1"; } .fa-file-word-o:before { content: "\f1c2"; } .fa-file-excel-o:before { content: "\f1c3"; } .fa-file-powerpoint-o:before { content: "\f1c4"; } .fa-file-photo-o:before, .fa-file-picture-o:before, .fa-file-image-o:before { content: "\f1c5"; } .fa-file-zip-o:before, .fa-file-archive-o:before { content: "\f1c6"; } .fa-file-sound-o:before, .fa-file-audio-o:before { content: "\f1c7"; } .fa-file-movie-o:before, .fa-file-video-o:before { content: "\f1c8"; } .fa-file-code-o:before { content: "\f1c9"; } .fa-vine:before { content: "\f1ca"; } .fa-codepen:before { content: "\f1cb"; } .fa-jsfiddle:before { content: "\f1cc"; } .fa-life-bouy:before, .fa-life-buoy:before, .fa-life-saver:before, .fa-support:before, .fa-life-ring:before { content: "\f1cd"; } .fa-circle-o-notch:before { content: "\f1ce"; } .fa-ra:before, .fa-rebel:before { content: "\f1d0"; } .fa-ge:before, .fa-empire:before { content: "\f1d1"; } .fa-git-square:before { content: "\f1d2"; } .fa-git:before { content: "\f1d3"; } .fa-y-combinator-square:before, .fa-yc-square:before, .fa-hacker-news:before { content: "\f1d4"; } .fa-tencent-weibo:before { content: "\f1d5"; } .fa-qq:before { content: "\f1d6"; } .fa-wechat:before, .fa-weixin:before { content: "\f1d7"; } .fa-send:before, .fa-paper-plane:before { content: "\f1d8"; } .fa-send-o:before, .fa-paper-plane-o:before { content: "\f1d9"; } .fa-history:before { content: "\f1da"; } .fa-circle-thin:before { content: "\f1db"; } .fa-header:before { content: "\f1dc"; } .fa-paragraph:before { content: "\f1dd"; } .fa-sliders:before { content: "\f1de"; } .fa-share-alt:before { content: "\f1e0"; } .fa-share-alt-square:before { content: "\f1e1"; } .fa-bomb:before { content: "\f1e2"; } .fa-soccer-ball-o:before, .fa-futbol-o:before { content: "\f1e3"; } .fa-tty:before { content: "\f1e4"; } .fa-binoculars:before { content: "\f1e5"; } .fa-plug:before { content: "\f1e6"; } .fa-slideshare:before { content: "\f1e7"; } .fa-twitch:before { content: "\f1e8"; } .fa-yelp:before { content: "\f1e9"; } .fa-newspaper-o:before { content: "\f1ea"; } .fa-wifi:before { content: "\f1eb"; } .fa-calculator:before { content: "\f1ec"; } .fa-paypal:before { content: "\f1ed"; } .fa-google-wallet:before { content: "\f1ee"; } .fa-cc-visa:before { content: "\f1f0"; } .fa-cc-mastercard:before { content: "\f1f1"; } .fa-cc-discover:before { content: "\f1f2"; } .fa-cc-amex:before { content: "\f1f3"; } .fa-cc-paypal:before { content: "\f1f4"; } .fa-cc-stripe:before { content: "\f1f5"; } .fa-bell-slash:before { content: "\f1f6"; } .fa-bell-slash-o:before { content: "\f1f7"; } .fa-trash:before { content: "\f1f8"; } .fa-copyright:before { content: "\f1f9"; } .fa-at:before { content: "\f1fa"; } .fa-eyedropper:before { content: "\f1fb"; } .fa-paint-brush:before { content: "\f1fc"; } .fa-birthday-cake:before { content: "\f1fd"; } .fa-area-chart:before { content: "\f1fe"; } .fa-pie-chart:before { content: "\f200"; } .fa-line-chart:before { content: "\f201"; } .fa-lastfm:before { content: "\f202"; } .fa-lastfm-square:before { content: "\f203"; } .fa-toggle-off:before { content: "\f204"; } .fa-toggle-on:before { content: "\f205"; } .fa-bicycle:before { content: "\f206"; } .fa-bus:before { content: "\f207"; } .fa-ioxhost:before { content: "\f208"; } .fa-angellist:before { content: "\f209"; } .fa-cc:before { content: "\f20a"; } .fa-shekel:before, .fa-sheqel:before, .fa-ils:before { content: "\f20b"; } .fa-meanpath:before { content: "\f20c"; } .fa-buysellads:before { content: "\f20d"; } .fa-connectdevelop:before { content: "\f20e"; } .fa-dashcube:before { content: "\f210"; } .fa-forumbee:before { content: "\f211"; } .fa-leanpub:before { content: "\f212"; } .fa-sellsy:before { content: "\f213"; } .fa-shirtsinbulk:before { content: "\f214"; } .fa-simplybuilt:before { content: "\f215"; } .fa-skyatlas:before { content: "\f216"; } .fa-cart-plus:before { content: "\f217"; } .fa-cart-arrow-down:before { content: "\f218"; } .fa-diamond:before { content: "\f219"; } .fa-ship:before { content: "\f21a"; } .fa-user-secret:before { content: "\f21b"; } .fa-motorcycle:before { content: "\f21c"; } .fa-street-view:before { content: "\f21d"; } .fa-heartbeat:before { content: "\f21e"; } .fa-venus:before { content: "\f221"; } .fa-mars:before { content: "\f222"; } .fa-mercury:before { content: "\f223"; } .fa-intersex:before, .fa-transgender:before { content: "\f224"; } .fa-transgender-alt:before { content: "\f225"; } .fa-venus-double:before { content: "\f226"; } .fa-mars-double:before { content: "\f227"; } .fa-venus-mars:before { content: "\f228"; } .fa-mars-stroke:before { content: "\f229"; } .fa-mars-stroke-v:before { content: "\f22a"; } .fa-mars-stroke-h:before { content: "\f22b"; } .fa-neuter:before { content: "\f22c"; } .fa-genderless:before { content: "\f22d"; } .fa-facebook-official:before { content: "\f230"; } .fa-pinterest-p:before { content: "\f231"; } .fa-whatsapp:before { content: "\f232"; } .fa-server:before { content: "\f233"; } .fa-user-plus:before { content: "\f234"; } .fa-user-times:before { content: "\f235"; } .fa-hotel:before, .fa-bed:before { content: "\f236"; } .fa-viacoin:before { content: "\f237"; } .fa-train:before { content: "\f238"; } .fa-subway:before { content: "\f239"; } .fa-medium:before { content: "\f23a"; } .fa-yc:before, .fa-y-combinator:before { content: "\f23b"; } .fa-optin-monster:before { content: "\f23c"; } .fa-opencart:before { content: "\f23d"; } .fa-expeditedssl:before { content: "\f23e"; } .fa-battery-4:before, .fa-battery-full:before { content: "\f240"; } .fa-battery-3:before, .fa-battery-three-quarters:before { content: "\f241"; } .fa-battery-2:before, .fa-battery-half:before { content: "\f242"; } .fa-battery-1:before, .fa-battery-quarter:before { content: "\f243"; } .fa-battery-0:before, .fa-battery-empty:before { content: "\f244"; } .fa-mouse-pointer:before { content: "\f245"; } .fa-i-cursor:before { content: "\f246"; } .fa-object-group:before { content: "\f247"; } .fa-object-ungroup:before { content: "\f248"; } .fa-sticky-note:before { content: "\f249"; } .fa-sticky-note-o:before { content: "\f24a"; } .fa-cc-jcb:before { content: "\f24b"; } .fa-cc-diners-club:before { content: "\f24c"; } .fa-clone:before { content: "\f24d"; } .fa-balance-scale:before { content: "\f24e"; } .fa-hourglass-o:before { content: "\f250"; } .fa-hourglass-1:before, .fa-hourglass-start:before { content: "\f251"; } .fa-hourglass-2:before, .fa-hourglass-half:before { content: "\f252"; } .fa-hourglass-3:before, .fa-hourglass-end:before { content: "\f253"; } .fa-hourglass:before { content: "\f254"; } .fa-hand-grab-o:before, .fa-hand-rock-o:before { content: "\f255"; } .fa-hand-stop-o:before, .fa-hand-paper-o:before { content: "\f256"; } .fa-hand-scissors-o:before { content: "\f257"; } .fa-hand-lizard-o:before { content: "\f258"; } .fa-hand-spock-o:before { content: "\f259"; } .fa-hand-pointer-o:before { content: "\f25a"; } .fa-hand-peace-o:before { content: "\f25b"; } .fa-trademark:before { content: "\f25c"; } .fa-registered:before { content: "\f25d"; } .fa-creative-commons:before { content: "\f25e"; } .fa-gg:before { content: "\f260"; } .fa-gg-circle:before { content: "\f261"; } .fa-tripadvisor:before { content: "\f262"; } .fa-odnoklassniki:before { content: "\f263"; } .fa-odnoklassniki-square:before { content: "\f264"; } .fa-get-pocket:before { content: "\f265"; } .fa-wikipedia-w:before { content: "\f266"; } .fa-safari:before { content: "\f267"; } .fa-chrome:before { content: "\f268"; } .fa-firefox:before { content: "\f269"; } .fa-opera:before { content: "\f26a"; } .fa-internet-explorer:before { content: "\f26b"; } .fa-tv:before, .fa-television:before { content: "\f26c"; } .fa-contao:before { content: "\f26d"; } .fa-500px:before { content: "\f26e"; } .fa-amazon:before { content: "\f270"; } .fa-calendar-plus-o:before { content: "\f271"; } .fa-calendar-minus-o:before { content: "\f272"; } .fa-calendar-times-o:before { content: "\f273"; } .fa-calendar-check-o:before { content: "\f274"; } .fa-industry:before { content: "\f275"; } .fa-map-pin:before { content: "\f276"; } .fa-map-signs:before { content: "\f277"; } .fa-map-o:before { content: "\f278"; } .fa-map:before { content: "\f279"; } .fa-commenting:before { content: "\f27a"; } .fa-commenting-o:before { content: "\f27b"; } .fa-houzz:before { content: "\f27c"; } .fa-vimeo:before { content: "\f27d"; } .fa-black-tie:before { content: "\f27e"; } .fa-fonticons:before { content: "\f280"; } .fa-reddit-alien:before { content: "\f281"; } .fa-edge:before { content: "\f282"; } .fa-credit-card-alt:before { content: "\f283"; } .fa-codiepie:before { content: "\f284"; } .fa-modx:before { content: "\f285"; } .fa-fort-awesome:before { content: "\f286"; } .fa-usb:before { content: "\f287"; } .fa-product-hunt:before { content: "\f288"; } .fa-mixcloud:before { content: "\f289"; } .fa-scribd:before { content: "\f28a"; } .fa-pause-circle:before { content: "\f28b"; } .fa-pause-circle-o:before { content: "\f28c"; } .fa-stop-circle:before { content: "\f28d"; } .fa-stop-circle-o:before { content: "\f28e"; } .fa-shopping-bag:before { content: "\f290"; } .fa-shopping-basket:before { content: "\f291"; } .fa-hashtag:before { content: "\f292"; } .fa-bluetooth:before { content: "\f293"; } .fa-bluetooth-b:before { content: "\f294"; } .fa-percent:before { content: "\f295"; } ================================================ FILE: web/client/resources/fonts/Open_Sans/LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: web/client/resources/fonts/Orbitron/OFL.txt ================================================ Copyright (c) 2009, Matt McInerney (matt@pixelspread.com), with Reserved Font Name Orbitron. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: web/client/resources/fonts/Oswald/OFL.txt ================================================ Copyright (c) 2011-2012, Vernon Adams (vern@newtypography.co.uk), with Reserved Font Names 'Oswald' This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: web/client/resources/js/composer.js ================================================ var composer = { tab: "\t", template: "", isFunc: function(scope) { if (!scope.title || typeof scope.depth === 'undefined') return false; return scope.title.indexOf("Test") === 0 && scope.depth === 0; }, discardLastKey: false }; $(function() { // Begin layout sizing var headerHeight = $('header').outerHeight(); var padding = $('#input, #output').css('padding-top').replace("px", "") * 2 + 1; var outputPlaceholder = $('#output').text(); $(window).resize(function() { $('#input, #output').height($(window).height() - headerHeight - padding); }); $(window).resize(); // End layout sizing $('#input').keydown(function(e) { // 13=Enter, 16=Shift composer.discardLastKey = e.keyCode === 13 || e.keyCode === 16; }).keyup(function(e) { if (!composer.discardLastKey) generate($(this).val()); }); composer.template = $('#tpl-convey').text(); tabOverride.set(document.getElementById('input')); $('#input').focus(); }); // Begin Markup.js custom pipes Mark.pipes.recursivelyRender = function(val) { return !val || val.length === 0 ? "\n" : Mark.up(composer.template, val); } Mark.pipes.indent = function(val) { return new Array(val + 1).join("\t"); } Mark.pipes.notTestFunc = function(scope) { return !composer.isFunc(scope); } Mark.pipes.safeFunc = function(val) { return val.replace(/[^a-z0-9_]/gi, ''); } Mark.pipes.properCase = function(str) { if (str.length === 0) return ""; str = str.charAt(0).toUpperCase() + str.substr(1); if (str.length < 2) return str; return str.replace(/[\s_][a-z]+/g, function(txt) { return txt.charAt(0) + txt.charAt(1).toUpperCase() + txt.substr(2).toLowerCase(); }); } Mark.pipes.showImports = function(item) { console.log(item); if (root.title === "(root)" && root.stories.length > 0) return 'import (\n\t"testing"\n\t. "github.com/smartystreets/goconvey/convey"\n)\n'; else return ""; } // End Markup.js custom pipes function generate(input) { var root = parseInput(input); $('#output').text(Mark.up(composer.template, root.stories)); if (root.stories.length > 0 && root.stories[0].title.substr(0, 4) === "Test") $('#output').prepend('import (\n\t"testing"\n\t. "github.com/smartystreets/goconvey/convey"\n)\n\n'); } function parseInput(input) { lines = input.split("\n"); if (!lines) return; var root = { title: "(root)", stories: [] }; for (i in lines) { line = lines[i]; lineText = $.trim(line); if (!lineText) continue; // Figure out how deep to put this story indent = line.match(new RegExp("^" + composer.tab + "+")); tabs = indent ? indent[0].length / composer.tab.length : 0; // Starting at root, traverse into the right spot in the arrays var curScope = root, prevScope = root; for (j = 0; j < tabs && curScope.stories.length > 0; j++) { curScope = curScope.stories[curScope.stories.length - 1]; prevScope = curScope; } // Don't go crazy, though! (avoid excessive indentation) if (tabs > curScope.depth + 1) tabs = curScope.depth + 1; // Only top-level Convey() calls need the *testing.T object passed in var showT = composer.isFunc(prevScope) || (!composer.isFunc(curScope) && tabs === 0); // Save the story at this scope curScope.stories.push({ title: lineText.replace(/"/g, "\\\""), // escape quotes stories: [], depth: tabs, showT: showT }); } return root; } function suppress(event) { if (!event) return false; if (event.preventDefault) event.preventDefault(); if (event.stopPropagation) event.stopPropagation(); event.cancelBubble = true; return false; } ================================================ FILE: web/client/resources/js/config.js ================================================ // Configure the GoConvey web UI client in here convey.config = { // Install new themes by adding them here; the first one will be default themes: { "dark": { name: "Dark", filename: "dark.css", coverage: "hsla({{hue}}, 75%, 30%, .5)" }, "dark-bigtext": { name: "Dark-BigText", filename: "dark-bigtext.css", coverage: "hsla({{hue}}, 75%, 30%, .5)" }, "light": { name: "Light", filename: "light.css", coverage: "hsla({{hue}}, 62%, 75%, 1)" } }, // Path to the themes (end with forward-slash) themePath: "/resources/css/themes/" }; ================================================ FILE: web/client/resources/js/convey.js ================================================ var convey = { // *** Don't edit in here unless you're brave *** statuses: { // contains some constants related to overall test status pass: { class: 'ok', text: "Pass" }, // class name must also be that in the favicon file name fail: { class: 'fail', text: "Fail" }, panic: { class: 'panic', text: "Panic" }, buildfail: { class: 'buildfail', text: "Build Failure" } }, frameCounter: 0, // gives each frame a unique ID maxHistory: 20, // how many tests to keep in the history notif: undefined, // the notification currently being displayed notifTimer: undefined, // the timer that clears the notifications automatically poller: new Poller(), // the server poller status: "", // what the _server_ is currently doing (not overall test results) overallClass: "", // class name of the "overall" status banner theme: "", // theme currently being used packageStates: {}, // packages manually collapsed or expanded during this page's lifetime uiEffects: true, // whether visual effects are enabled framesOnSamePath: 0, // number of consecutive frames on this same watch path layout: { selClass: "sel", // CSS class when an element is "selected" header: undefined, // container element of the header area (overall, controls) frame: undefined, // container element of the main body area (above footer) footer: undefined // container element of the footer (stuck to bottom) }, history: [], // complete history of states (test results and aggregated data), including the current one moments: {}, // elements that display time relative to the current time, keyed by ID, with the moment() as a value intervals: {}, // ntervals that execute periodically intervalFuncs: { // functions executed by each interval in convey.intervals time: function() { var t = new Date(); var h = zerofill(t.getHours(), 2); var m = zerofill(t.getMinutes(), 2); var s = zerofill(t.getSeconds(), 2); $('#time').text(h + ":" + m + ":" + s); }, momentjs: function() { for (var id in convey.moments) $('#'+id).html(convey.moments[id].fromNow()); } } }; ================================================ FILE: web/client/resources/js/goconvey.js ================================================ $(init); $(window).load(function() { // Things may shift after all the elements (images/fonts) are loaded // In Chrome, calling reframe() doesn't work (maybe a quirk); we need to trigger resize $(window).resize(); }); function init() { log("Welcome to GoConvey!"); log("Initializing interface"); convey.overall = emptyOverall(); loadTheme(); $('body').show(); initPoller(); wireup(); latest(); } function loadTheme(thmID) { var defaultTheme = "dark"; var linkTagId = "themeRef"; if (!thmID) thmID = get('theme') || defaultTheme; log("Initializing theme: " + thmID); if (!convey.config.themes[thmID]) { replacement = Object.keys(convey.config.themes)[0] || defaultTheme; log("NOTICE: Could not find '" + thmID + "' theme; defaulting to '" + replacement + "'"); thmID = replacement; } convey.theme = thmID; save('theme', convey.theme); var linkTag = $('#'+linkTagId); var fullPath = convey.config.themePath + convey.config.themes[convey.theme].filename; if (linkTag.length === 0) { $('head').append(''); } else linkTag.attr('href', fullPath); colorizeCoverageBars(); } function initPoller() { $(convey.poller).on('serverstarting', function(event) { log("Server is starting..."); convey.status = "starting"; showServerDown("Server starting"); $('#run-tests').addClass('spin-slowly disabled'); }); $(convey.poller).on('pollsuccess', function(event, data) { if (convey.status !== "starting") hideServerDown(); // These two if statements determine if the server is now busy // (and wasn't before) or is not busy (regardless of whether it was before) if ((!convey.status || convey.status === "idle") && data.status && data.status !== "idle") $('#run-tests').addClass('spin-slowly disabled'); else if (convey.status !== "idle" && data.status === "idle") { $('#run-tests').removeClass('spin-slowly disabled'); } switch (data.status) { case "executing": $(convey.poller).trigger('serverexec', data); break; case "idle": $(convey.poller).trigger('serveridle', data); break; } convey.status = data.status; }); $(convey.poller).on('pollfail', function(event, data) { log("Poll failed; server down"); convey.status = "down"; showServerDown("Server down"); }); $(convey.poller).on('serverexec', function(event, data) { log("Server status: executing"); $('.favicon').attr('href', '/favicon.ico'); // indicates running tests }); $(convey.poller).on('serveridle', function(event, data) { log("Server status: idle"); log("Tests have finished executing"); latest(); }); convey.poller.start(); } function wireup() { log("Wireup"); customMarkupPipes(); var themes = []; for (var k in convey.config.themes) themes.push({ id: k, name: convey.config.themes[k].name }); $('#theme').html(render('tpl-theme-enum', themes)); enumSel("theme", convey.theme); loadSettingsFromStorage(); $('#stories').on('click', '.toggle-all-pkg', function(event) { if ($(this).closest('.story-pkg').data('pkg-state') === "expanded") collapseAll(); else expandAll(); return suppress(event); }); // Wireup the settings switches $('.enum#theme').on('click', 'li:not(.sel)', function() { loadTheme($(this).data('theme')); }); $('.enum#pkg-expand-collapse').on('click', 'li:not(.sel)', function() { var newSetting = $(this).data('pkg-expand-collapse'); convey.packageStates = {}; save('pkg-expand-collapse', newSetting); if (newSetting === "expanded") expandAll(); else collapseAll(); }); $('.enum#show-debug-output').on('click', 'li:not(.sel)', function() { var newSetting = $(this).data('show-debug-output'); save('show-debug-output', newSetting); setDebugOutputUI(newSetting); }); $('.enum#ui-effects').on('click', 'li:not(.sel)', function() { var newSetting = $(this).data('ui-effects'); convey.uiEffects = newSetting; save('ui-effects', newSetting); }); // End settings wireup //wireup the notification-settings switches $('.enum#notification').on('click', 'li:not(.sel)', function() { var enabled = $(this).data('notification'); log("Turning notifications " + enabled ? 'on' : 'off'); save('notifications', enabled); if (notif() && 'Notification' in window) { if (Notification.permission !== 'denied') { Notification.requestPermission(function(per) { if (!('permission' in Notification)) { Notification.permission = per; } }); } else log("Permission denied to show desktop notification"); } setNotifUI() }); $('.enum#notification-level').on('click', 'li:not(.sel)', function() { var level = $(this).data('notification-level'); convey.notificationLevel = level; save('notification-level', level); }); // End notification-settings convey.layout.header = $('header').first(); convey.layout.frame = $('.frame').first(); convey.layout.footer = $('footer').last(); updateWatchPath(); $('#path').change(function() { // Updates the watched directory with the server and makes sure it exists var tb = $(this); var newpath = encodeURIComponent($.trim(tb.val())); $.post('/watch?root='+newpath) .done(function() { tb.removeClass('error'); }) .fail(function() { tb.addClass('error'); }); convey.framesOnSamePath = 1; }); $('#run-tests').click(function() { var self = $(this); if (self.hasClass('spin-slowly') || self.hasClass('disabled')) return; log("Test run invoked from web UI"); $.get("/execute"); }); $('#play-pause').click(function() { $.get('/pause'); if ($(this).hasClass(convey.layout.selClass)) { // Un-pausing if (!$('footer .replay').is(':visible')) $('footer .recording').show(); $('footer .paused').hide(); log("Resuming auto-execution of tests"); } else { // Pausing $('footer .recording').hide(); $('footer .paused').show(); log("Pausing auto-execution of tests"); } $(this).toggleClass("throb " + convey.layout.selClass); }); $('#toggle-notif').click(function() { toggle($('.settings-notification'), $(this)); }); $('#show-history').click(function() { toggle($('.history'), $(this)); }); $('#show-settings').click(function() { toggle($('.settings-general'), $(this)); }); $('#show-gen').click(function() { var writer = window.open("/composer.html"); if (window.focus) writer.focus(); }); $('.toggler').not('.narrow').prepend(''); $('.toggler.narrow').prepend(''); $('.toggler').not('.narrow').click(function() { var target = $('#' + $(this).data('toggle')); $('.fa-angle-down, .fa-angle-up', this).toggleClass('fa-angle-down fa-angle-up'); target.toggle(); }); $('.toggler.narrow').click(function() { var target = $('#' + $(this).data('toggle')); $('.fa-angle-down, .fa-angle-up', this).toggleClass('fa-angle-down fa-angle-up'); target.toggleClass('hide-narrow show-narrow'); }); // Enumerations are horizontal lists where one item can be selected at a time $('.enum').on('click', 'li', enumSel); // Start ticking time convey.intervals.time = setInterval(convey.intervalFuncs.time, 1000); convey.intervals.momentjs = setInterval(convey.intervalFuncs.momentjs, 5000); convey.intervalFuncs.time(); // Ignore/un-ignore package $('#stories').on('click', '.fa.ignore', function(event) { var pkg = $(this).data('pkg'); if ($(this).hasClass('disabled')) return; else if ($(this).hasClass('unwatch')) $.get("/ignore", { paths: pkg }); else $.get("/reinstate", { paths: pkg }); $(this).toggleClass('watch unwatch fa-eye fa-eye-slash clr-red'); return suppress(event); }); // Show "All" link when hovering the toggler on packages in the stories $('#stories').on({ mouseenter: function() { $('.toggle-all-pkg', this).stop().show('fast'); }, mouseleave: function() { $('.toggle-all-pkg', this).stop().hide('fast'); } }, '.pkg-toggle-container'); // Toggle a package in the stories when clicked $('#stories').on('click', '.story-pkg', function(event) { togglePackage(this, true); return suppress(event); }); // Select a story line when it is clicked $('#stories').on('click', '.story-line', function() { $('.story-line-sel').not(this).removeClass('story-line-sel'); $(this).toggleClass('story-line-sel'); }); // Render a frame from the history when clicked $('.history .container').on('click', '.item', function(event) { var frame = getFrame($(this).data("frameid")); changeStatus(frame.overall.status, true); renderFrame(frame); $(this).addClass('selected'); // Update current status down in the footer if ($(this).is(':first-child')) { // Now on current frame $('footer .replay').hide(); if ($('#play-pause').hasClass(convey.layout.selClass)) // Was/is paused $('footer .paused').show(); else $('footer .recording').show(); // Was/is recording } else { $('footer .recording, footer .replay').hide(); $('footer .replay').show(); } return suppress(event); }); $('footer').on('click', '.replay', function() { // Clicking "REPLAY" in the corner should bring them back to the current frame // and hide, if visible, the history panel for convenience $('.history .item:first-child').click(); if ($('#show-history').hasClass('sel')) $('#show-history').click(); }); // Keyboard shortcuts! $(document).keydown(function(e) { if (e.ctrlKey || e.metaKey || e.shiftKey) return; switch (e.keyCode) { case 67: // c $('#show-gen').click(); break; case 82: // r $('#run-tests').click(); break; case 78: // n $('#toggle-notif').click(); break; case 87: // w $('#path').focus(); break; case 80: // p $('#play-pause').click(); break; } return suppress(e); }); $('body').on('keydown', 'input, textarea, select', function(e) { // If user is typing something, don't let this event bubble // up to the document to annoyingly fire keyboard shortcuts e.stopPropagation(); }); // Wire-up the tipsy tooltips setTooltips(); // Keep everything positioned and sized properly on window resize reframe(); $(window).resize(reframe); } function setTooltips() { var tips = { '#path': { delayIn: 500 }, '#logo': { gravity: 'w' }, '.controls li, .pkg-cover-name': { live: false }, 'footer .replay': { live: false, gravity: 'e' }, '.ignore': { live: false, gravity: $.fn.tipsy.autoNS }, '.disabled': { live: false, gravity: $.fn.tipsy.autoNS } }; for (var key in tips) { $(key).each(function(el) { if(!$(this).tipsy(true)) $(this).tipsy(tips[key]); }); } } function setDebugOutputUI(newSetting){ var $storyLine = $('.story-line'); switch(newSetting) { case 'hide': $('.message', $storyLine).hide(); break; case 'fail': $('.message', $storyLine.not('.fail, .panic')).hide(); $('.message', $storyLine.filter('.fail, .panic')).show(); break; default: $('.message', $storyLine).show(); break; } } function setNotifUI() { var $toggleNotif = $('#toggle-notif').addClass(notif() ? "fa-bell" : "fa-bell-o"); $toggleNotif.removeClass(!notif() ? "fa-bell" : "fa-bell-o"); } function expandAll() { $('.story-pkg').each(function() { expandPackage($(this).data('pkg')); }); } function collapseAll() { $('.story-pkg').each(function() { collapsePackage($(this).data('pkg')); }); } function expandPackage(pkgId) { var pkg = $('.story-pkg.pkg-'+pkgId); var rows = $('.story-line.pkg-'+pkgId); pkg.data('pkg-state', "expanded").addClass('expanded').removeClass('collapsed'); $('.pkg-toggle', pkg) .addClass('fa-minus-square-o') .removeClass('fa-plus-square-o'); rows.show(); } function collapsePackage(pkgId) { var pkg = $('.story-pkg.pkg-'+pkgId); var rows = $('.story-line.pkg-'+pkgId); pkg.data('pkg-state', "collapsed").addClass('collapsed').removeClass('expanded'); $('.pkg-toggle', pkg) .addClass('fa-plus-square-o') .removeClass('fa-minus-square-o'); rows.hide(); } function togglePackage(storyPkgElem) { var pkgId = $(storyPkgElem).data('pkg'); if ($(storyPkgElem).data('pkg-state') === "expanded") { collapsePackage(pkgId); convey.packageStates[$(storyPkgElem).data('pkg-name')] = "collapsed"; } else { expandPackage(pkgId); convey.packageStates[$(storyPkgElem).data('pkg-name')] = "expanded"; } } function loadSettingsFromStorage() { var pkgExpCollapse = get("pkg-expand-collapse"); if (!pkgExpCollapse) { pkgExpCollapse = "expanded"; save("pkg-expand-collapse", pkgExpCollapse); } enumSel("pkg-expand-collapse", pkgExpCollapse); var showDebugOutput = get("show-debug-output"); if (!showDebugOutput) { showDebugOutput = "show"; save("show-debug-output", showDebugOutput); } enumSel("show-debug-output", showDebugOutput); var uiEffects = get("ui-effects"); if (uiEffects === null) uiEffects = "true"; convey.uiEffects = uiEffects === "true"; enumSel("ui-effects", uiEffects); enumSel("notification", ""+notif()); var notifLevel = get("notification-level"); if (notifLevel === null) { notifLevel = '.*'; } convey.notificationLevel = notifLevel; enumSel("notification-level", notifLevel); setNotifUI(); } function latest() { log("Fetching latest test results"); $.getJSON("/latest", process); } function process(data, status, jqxhr) { if (!data || !data.Revision) { log("No data received or revision timestamp was missing"); return; } if (data.Paused && !$('#play-pause').hasClass(convey.layout.selClass)) { $('footer .recording').hide(); $('footer .paused').show(); $('#play-pause').toggleClass("throb " + convey.layout.selClass); } if (current() && data.Revision === current().results.Revision) { log("No changes"); changeStatus(current().overall.status); // re-assures that status is unchanged return; } // Put the new frame in the queue so we can use current() to get to it convey.history.push(newFrame()); convey.framesOnSamePath++; // Store the raw results in our frame current().results = data; log("Updating watch path"); updateWatchPath(); // Remove all templated items from the DOM as we'll // replace them with new ones; also remove tipsy tooltips // that may have lingered around $('.templated, .tipsy').remove(); var uniqueID = 0; var coverageAvgHelper = { countedPackages: 0, coverageSum: 0 }; var packages = { tested: [], ignored: [], coverage: {}, nogofiles: [], notestfiles: [], notestfn: [] }; log("Compiling package statistics"); // Look for failures and panics through the packages->tests->stories... for (var i in data.Packages) { pkg = makeContext(data.Packages[i]); current().overall.duration += pkg.Elapsed; pkg._id = uniqueID++; if (pkg.Outcome === "build failure") { current().overall.failedBuilds++; current().failedBuilds.push(pkg); continue; } if (pkg.Outcome === "no go code") packages.nogofiles.push(pkg); else if (pkg.Outcome === "no test files") packages.notestfiles.push(pkg); else if (pkg.Outcome === "no test functions") packages.notestfn.push(pkg); else if (pkg.Outcome === "ignored" || pkg.Outcome === "disabled") packages.ignored.push(pkg); else { if (pkg.Coverage >= 0) coverageAvgHelper.coverageSum += pkg.Coverage; coverageAvgHelper.countedPackages++; packages.coverage[pkg.PackageName] = pkg.Coverage; packages.tested.push(pkg); } for (var j in pkg.TestResults) { test = makeContext(pkg.TestResults[j]); test._id = uniqueID++; test._pkgid = pkg._id; test._pkg = pkg.PackageName; if (test.Stories.length === 0) { // Here we've got ourselves a classic Go test, // not a GoConvey test that has stories and assertions // so we'll treat this whole test as a single assertion current().overall.assertions++; if (test.Error) { test._status = convey.statuses.panic; pkg._panicked++; test._panicked++; current().assertions.panicked.push(test); } else if (test.Passed === false) { test._status = convey.statuses.fail; pkg._failed++; test._failed++; current().assertions.failed.push(test); } else if (test.Skipped) { test._status = convey.statuses.skipped; pkg._skipped++; test._skipped++; current().assertions.skipped.push(test); } else { test._status = convey.statuses.pass; pkg._passed++; test._passed++; current().assertions.passed.push(test); } } else test._status = convey.statuses.pass; var storyPath = [{ Depth: -1, Title: test.TestName, _id: test._id }]; // Maintains the current assertion's story as we iterate for (var k in test.Stories) { var story = makeContext(test.Stories[k]); story._id = uniqueID; story._pkgid = pkg._id; current().overall.assertions += story.Assertions.length; // Establish the current story path so we can report the context // of failures and panicks more conveniently at the top of the page if (storyPath.length > 0) for (var x = storyPath[storyPath.length - 1].Depth; x >= test.Stories[k].Depth; x--) storyPath.pop(); storyPath.push({ Depth: test.Stories[k].Depth, Title: test.Stories[k].Title, _id: test.Stories[k]._id }); for (var l in story.Assertions) { var assertion = story.Assertions[l]; assertion._id = uniqueID; assertion._pkg = pkg.PackageName; assertion._pkgId = pkg._id; assertion._failed = !!assertion.Failure; assertion._panicked = !!assertion.Error; assertion._maxDepth = storyPath[storyPath.length - 1].Depth; $.extend(assertion._path = [], storyPath); if (assertion.Failure) { current().assertions.failed.push(assertion); pkg._failed++; test._failed++; story._failed++; } if (assertion.Error) { current().assertions.panicked.push(assertion); pkg._panicked++; test._panicked++; story._panicked++; } if (assertion.Skipped) { current().assertions.skipped.push(assertion); pkg._skipped++; test._skipped++; story._skipped++; } if (!assertion.Failure && !assertion.Error && !assertion.Skipped) { current().assertions.passed.push(assertion); pkg._passed++; test._passed++; story._passed++; } } assignStatus(story); uniqueID++; } if (!test.Passed && !test._failed && !test._panicked) { // Edge case: Developer is using the GoConvey DSL, but maybe // in some cases is using t.Error() instead of So() assertions. // This can be detected, assuming all child stories with // assertions (in this test) are passing. test._status = convey.statuses.fail; pkg._failed++; test._failed++; current().assertions.failed.push(test); } } } current().overall.passed = current().assertions.passed.length; current().overall.panics = current().assertions.panicked.length; current().overall.failures = current().assertions.failed.length; current().overall.skipped = current().assertions.skipped.length; current().overall.coverage = Math.round((coverageAvgHelper.coverageSum / (coverageAvgHelper.countedPackages || 1)) * 100) / 100; current().overall.duration = Math.round(current().overall.duration * 1000) / 1000; // Compute the coverage delta (difference in overall coverage between now and last frame) // Only compare coverage on the same watch path var coverDelta = current().overall.coverage; if (convey.framesOnSamePath > 2) coverDelta = current().overall.coverage - convey.history[convey.history.length - 2].overall.coverage; current().coverDelta = Math.round(coverDelta * 100) / 100; // Build failures trump panics, // Panics trump failures, // Failures trump pass. if (current().overall.failedBuilds) changeStatus(convey.statuses.buildfail); else if (current().overall.panics) changeStatus(convey.statuses.panic); else if (current().overall.failures) changeStatus(convey.statuses.fail); else changeStatus(convey.statuses.pass); // Save our organized package lists current().packages = packages; log(" Assertions: " + current().overall.assertions); log(" Passed: " + current().overall.passed); log(" Skipped: " + current().overall.skipped); log(" Failures: " + current().overall.failures); log(" Panics: " + current().overall.panics); log("Build Failures: " + current().overall.failedBuilds); log(" Coverage: " + current().overall.coverage + "% (" + showCoverDelta(current().coverDelta) + ")"); // Save timestamp when this test was executed convey.moments['last-test'] = moment(); // Render... render ALL THE THINGS! (All model/state modifications are DONE!) renderFrame(current()); // Now, just finish up miscellaneous UI things // Add this frame to the history pane var framePiece = render('tpl-history', current()); $('.history .container').prepend(framePiece); $('.history .item:first-child').addClass('selected'); convey.moments['frame-'+current().id] = moment(); if (convey.history.length > convey.maxHistory) { // Delete the oldest frame out of the history pane if we have too many convey.history.splice(0, 1); $('.history .container .item').last().remove(); } // Now add the momentjs time to the new frame in the history convey.intervalFuncs.momentjs(); // Show notification, if enabled var levelRegex = new RegExp("("+convey.notificationLevel+")", "i"); if (notif() && current().overall.status.class.match(levelRegex)) { log("Showing notification"); if (convey.notif) { clearTimeout(convey.notifTimer); convey.notif.close(); } var notifText = notifSummary(current()); convey.notif = new Notification(notifText.title, { body: notifText.body, icon: $('.favicon').attr('href') }); convey.notif.onclick = function() { window.focus(); }; convey.notifTimer = setTimeout(function() { convey.notif.close(); }, 5000); } // Update title in title bar if (current().overall.passed === current().overall.assertions && current().overall.status.class === "ok") $('title').text("GoConvey (ALL PASS)"); else $('title').text("GoConvey [" + current().overall.status.text + "] " + current().overall.passed + "/" + current().overall.assertions); setTooltips(); // All done! log("Processing complete"); } // Updates the entire UI given a frame from the history function renderFrame(frame) { log("Rendering frame (id: " + frame.id + ")"); $('#coverage').html(render('tpl-coverage', frame.packages.tested.sort(sortPackages))); $('#ignored').html(render('tpl-ignored', frame.packages.ignored.sort(sortPackages))); $('#nogofiles').html(render('tpl-nogofiles', frame.packages.nogofiles.sort(sortPackages))); $('#notestfiles').html(render('tpl-notestfiles', frame.packages.notestfiles.sort(sortPackages))); $('#notestfn').html(render('tpl-notestfn', frame.packages.notestfn.sort(sortPackages))); if (frame.overall.failedBuilds) { $('.buildfailures').show(); $('#buildfailures').html(render('tpl-buildfailures', frame.failedBuilds)); } else $('.buildfailures').hide(); if (frame.overall.panics) { $('.panics').show(); $('#panics').html(render('tpl-panics', frame.assertions.panicked)); } else $('.panics').hide(); if (frame.overall.failures) { $('.failures').show(); $('#failures').html(render('tpl-failures', frame.assertions.failed)); $(".failure").each(function() { $(this).prettyTextDiff(); }); } else $('.failures').hide(); $('#stories').html(render('tpl-stories', frame.packages.tested.sort(sortPackages))); $('#stories').append(render('tpl-stories', frame.packages.ignored.sort(sortPackages))); var pkgDefaultView = get('pkg-expand-collapse'); $('.story-pkg.expanded').each(function() { if (pkgDefaultView === "collapsed" && convey.packageStates[$(this).data('pkg-name')] !== "expanded") collapsePackage($(this).data('pkg')); }); redrawCoverageBars(); $('#assert-count').html(""+frame.overall.assertions+" assertion" + (frame.overall.assertions !== 1 ? "s" : "")); $('#skip-count').html(""+frame.assertions.skipped.length + " skipped"); $('#fail-count').html(""+frame.assertions.failed.length + " failed"); $('#panic-count').html(""+frame.assertions.panicked.length + " panicked"); $('#duration').html(""+frame.overall.duration + "s"); $('#narrow-assert-count').html(""+frame.overall.assertions+""); $('#narrow-skip-count').html(""+frame.assertions.skipped.length + ""); $('#narrow-fail-count').html(""+frame.assertions.failed.length + ""); $('#narrow-panic-count').html(""+frame.assertions.panicked.length + ""); $('.history .item').removeClass('selected'); setDebugOutputUI(get('show-debug-output')); log("Rendering finished"); } function enumSel(id, val) { if (typeof id === "string" && typeof val === "string") { $('.enum#'+id+' > li').each(function() { if ($(this).data(id).toString() === val) { $(this).addClass(convey.layout.selClass).siblings().removeClass(convey.layout.selClass); return false; } }); } else $(this).addClass(convey.layout.selClass).siblings().removeClass(convey.layout.selClass); } function toggle(jqelem, switchelem) { var speed = 250; var transition = 'easeInOutQuart'; var containerSel = '.container'; if (!jqelem.is(':visible')) { $(containerSel, jqelem).css('opacity', 0); jqelem.stop().slideDown(speed, transition, function() { if (switchelem) switchelem.toggleClass(convey.layout.selClass); $(containerSel, jqelem).stop().animate({ opacity: 1 }, speed); reframe(); }); } else { $(containerSel, jqelem).stop().animate({ opacity: 0 }, speed, function() { if (switchelem) switchelem.toggleClass(convey.layout.selClass); jqelem.stop().slideUp(speed, transition, function() { reframe(); }); }); } } function changeStatus(newStatus, isHistoricalFrame) { if (!newStatus || !newStatus.class || !newStatus.text) newStatus = convey.statuses.pass; var sameStatus = newStatus.class === convey.overallClass; // The CSS class .flash and the jQuery UI 'pulsate' effect don't play well together. // This series of callbacks does the flickering/pulsating as well as // enabling/disabling flashing in the proper order so that they don't overlap. // TODO: I suppose the pulsating could also be done with just CSS, maybe...? if (convey.uiEffects) { var times = sameStatus ? 3 : 2; var duration = sameStatus ? 500 : 300; $('.overall .status').removeClass('flash').effect("pulsate", {times: times}, duration, function() { $(this).text(newStatus.text); if (newStatus !== convey.statuses.pass) // only flicker extra when not currently passing { $(this).effect("pulsate", {times: 1}, 300, function() { $(this).effect("pulsate", {times: 1}, 500, function() { if (newStatus === convey.statuses.panic || newStatus === convey.statuses.buildfail) $(this).addClass('flash'); else $(this).removeClass('flash'); }); }); } }); } else $('.overall .status').text(newStatus.text); if (!sameStatus) // change the color $('.overall').switchClass(convey.overallClass, newStatus.class, 1000); if (!isHistoricalFrame) current().overall.status = newStatus; convey.overallClass = newStatus.class; $('.favicon').attr('href', '/resources/ico/goconvey-'+newStatus.class+'.ico'); } function updateWatchPath() { $.get("/watch", function(data) { var newPath = $.trim(data); if (newPath !== $('#path').val()) convey.framesOnSamePath = 1; $('#path').val(newPath); }); } function notifSummary(frame) { var body = frame.overall.passed + " passed, "; if (frame.overall.failedBuilds) body += frame.overall.failedBuilds + " build" + (frame.overall.failedBuilds !== 1 ? "s" : "") + " failed, "; if (frame.overall.failures) body += frame.overall.failures + " failed, "; if (frame.overall.panics) body += frame.overall.panics + " panicked, "; body += frame.overall.skipped + " skipped"; body += "\r\n" + frame.overall.duration + "s"; if (frame.coverDelta > 0) body += "\r\n↑ coverage (" + showCoverDelta(frame.coverDelta) + ")"; else if (frame.coverDelta < 0) body += "\r\n↓ coverage (" + showCoverDelta(frame.coverDelta) + ")"; return { title: frame.overall.status.text.toUpperCase(), body: body }; } function redrawCoverageBars() { $('.pkg-cover-bar').each(function() { var pkgName = $(this).data("pkg"); var hue = $(this).data("width"); var hueDiff = hue; if (convey.history.length > 1) { var oldHue = convey.history[convey.history.length - 2].packages.coverage[pkgName] || 0; $(this).width(oldHue + "%"); hueDiff = hue - oldHue; } $(this).animate({ width: "+=" + hueDiff + "%" }, 1250); }); colorizeCoverageBars(); } function colorizeCoverageBars() { var colorTpl = convey.config.themes[convey.theme].coverage || "hsla({{hue}}, 75%, 30%, .3)"; //default color template $('.pkg-cover-bar').each(function() { var hue = $(this).data("width"); $(this).css({ background: colorTpl.replace("{{hue}}", hue) }); }); } function getFrame(id) { for (var i in convey.history) if (convey.history[i].id === id) return convey.history[i]; } function render(templateID, context) { var tpl = $('#' + templateID).text(); return $($.trim(Mark.up(tpl, context))); } function reframe() { var heightBelowHeader = $(window).height() - convey.layout.header.outerHeight(); var middleHeight = heightBelowHeader - convey.layout.footer.outerHeight(); convey.layout.frame.height(middleHeight); var pathWidth = $(window).width() - $('#logo').outerWidth() - $('#control-buttons').outerWidth() - 10; $('#path-container').width(pathWidth); } function notif() { return get('notifications') === "true"; // stored as strings } function showServerDown(message) { $('.server-down .notice-message').text(message); $('.server-down').show(); $('.server-not-down').hide(); reframe(); } function hideServerDown() { $('.server-down').hide(); $('.server-not-down').show(); reframe(); } function log(msg) { var jqLog = $('#log'); if (jqLog.length > 0) { var t = new Date(); var h = zerofill(t.getHours(), 2); var m = zerofill(t.getMinutes(), 2); var s = zerofill(t.getSeconds(), 2); var ms = zerofill(t.getMilliseconds(), 3); date = h + ":" + m + ":" + s + "." + ms; $(jqLog).append(render('tpl-log-line', { time: date, msg: msg })); $(jqLog).parent('.col').scrollTop(jqLog[0].scrollHeight); } else console.log(msg); } function zerofill(val, count) { // Cheers to http://stackoverflow.com/a/9744576/1048862 var pad = new Array(1 + count).join('0'); return (pad + val).slice(-pad.length); } // Sorts packages ascending by only the last part of their name // Can be passed into Array.sort() function sortPackages(a, b) { var aPkg = splitPathName(a.PackageName); var bPkg = splitPathName(b.PackageName); if (aPkg.length === 0 || bPkg.length === 0) return 0; var aName = aPkg.parts[aPkg.parts.length - 1].toLowerCase(); var bName = bPkg.parts[bPkg.parts.length - 1].toLowerCase(); if (aName < bName) return -1; else if (aName > bName) return 1; else return 0; /* MEMO: Use to sort by entire package name: if (a.PackageName < b.PackageName) return -1; else if (a.PackageName > b.PackageName) return 1; else return 0; */ } function get(key) { var val = localStorage.getItem(key); if (val && (val[0] === '[' || val[0] === '{')) return JSON.parse(val); else return val; } function save(key, val) { if (typeof val === 'object') val = JSON.stringify(val); else if (typeof val === 'number' || typeof val === 'boolean') val = val.toString(); localStorage.setItem(key, val); } function splitPathName(str) { var delim = str.indexOf('\\') > -1 ? '\\' : '/'; return { delim: delim, parts: str.split(delim) }; } function newFrame() { return { results: {}, // response from server (with some of our own context info) packages: {}, // packages organized into statuses for convenience (like with coverage) overall: emptyOverall(), // overall status info, compiled from server's response assertions: emptyAssertions(), // lists of assertions, compiled from server's response failedBuilds: [], // list of packages that failed to build timestamp: moment(), // the timestamp of this "freeze-state" id: convey.frameCounter++, // unique ID for this frame coverDelta: 0 // difference in total coverage from the last frame to this one }; } function emptyOverall() { return { status: {}, duration: 0, assertions: 0, passed: 0, panics: 0, failures: 0, skipped: 0, failedBuilds: 0, coverage: 0 }; } function emptyAssertions() { return { passed: [], failed: [], panicked: [], skipped: [] }; } function makeContext(obj) { obj._passed = 0; obj._failed = 0; obj._panicked = 0; obj._skipped = 0; obj._status = ''; return obj; } function current() { return convey.history[convey.history.length - 1]; } function assignStatus(obj) { if (obj._skipped) obj._status = 'skip'; else if (obj.Outcome === "ignored") obj._status = convey.statuses.ignored; else if (obj._panicked) obj._status = convey.statuses.panic; else if (obj._failed || obj.Outcome === "failed") obj._status = convey.statuses.fail; else obj._status = convey.statuses.pass; } function showCoverDelta(delta) { if (delta > 0) return "+" + delta + "%"; else if (delta === 0) return "±" + delta + "%"; else return delta + "%"; } function customMarkupPipes() { // MARKUP.JS custom pipes Mark.pipes.relativePath = function(str) { basePath = new RegExp($('#path').val()+'[\\/]', 'gi'); return str.replace(basePath, ''); }; Mark.pipes.htmlSafe = function(str) { return str.replace(//g, ">"); }; Mark.pipes.ansiColours = ansispan; Mark.pipes.boldPkgName = function(str) { var pkg = splitPathName(str); pkg.parts[0] = '' + pkg.parts[0]; pkg.parts[pkg.parts.length - 1] = "" + pkg.parts[pkg.parts.length - 1] + ""; return pkg.parts.join(pkg.delim); }; Mark.pipes.needsDiff = function(test) { return !!test.Failure && (test.Expected !== "" || test.Actual !== ""); }; Mark.pipes.coveragePct = function(str) { // Expected input: 75% to be represented as: "75.0" var num = parseInt(str); // we only need int precision if (num < 0) return "0"; else if (num <= 5) return "5"; // Still shows low coverage else if (num > 100) str = "100"; return str; }; Mark.pipes.coverageDisplay = function(str) { var num = parseFloat(str); return num < 0 ? "" : num + "% coverage"; }; Mark.pipes.coverageReportName = function(str) { return str.replace(/\//g, "-"); }; } function suppress(event) { if (!event) return false; if (event.preventDefault) event.preventDefault(); if (event.stopPropagation) event.stopPropagation(); event.cancelBubble = true; return false; } ================================================ FILE: web/client/resources/js/lib/ansispan.js ================================================ /* Copyright (C) 2011 by Maciej Małecki Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var ansispan = function (str) { Object.keys(ansispan.foregroundColors).forEach(function (ansi) { var span = ''; // // `\033[Xm` == `\033[0;Xm` sets foreground color to `X`. // str = str.replace( new RegExp('\033\\[' + ansi + 'm', 'g'), span ).replace( new RegExp('\033\\[0;' + ansi + 'm', 'g'), span ); }); // // `\033[1m` enables bold font, `\033[22m` disables it // str = str.replace(/\033\[1m/g, '').replace(/\033\[22m/g, ''); // // `\033[3m` enables italics font, `\033[23m` disables it // str = str.replace(/\033\[3m/g, '').replace(/\033\[23m/g, ''); str = str.replace(/\033\[m/g, ''); str = str.replace(/\033\[0m/g, ''); return str.replace(/\033\[39m/g, ''); }; ansispan.foregroundColors = { '30': 'black', '31': 'red', '32': 'green', '33': 'yellow', '34': 'blue', '35': 'purple', '36': 'cyan', '37': 'white' }; if (typeof module !== 'undefined' && module.exports) { module.exports = ansispan; } ================================================ FILE: web/client/resources/js/lib/diff_match_patch.js ================================================ /** * Diff Match and Patch * * Copyright 2006 Google Inc. * http://code.google.com/p/google-diff-match-patch/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview Computes the difference between two texts to create a patch. * Applies the patch onto another text, allowing for errors. * @author fraser@google.com (Neil Fraser) */ /** * Class containing the diff, match and patch methods. * @constructor */ function diff_match_patch() { // Defaults. // Redefine these in your program to override the defaults. // Number of seconds to map a diff before giving up (0 for infinity). this.Diff_Timeout = 1.0; // Cost of an empty edit operation in terms of edit characters. this.Diff_EditCost = 4; // At what point is no match declared (0.0 = perfection, 1.0 = very loose). this.Match_Threshold = 0.5; // How far to search for a match (0 = exact location, 1000+ = broad match). // A match this many characters away from the expected location will add // 1.0 to the score (0.0 is a perfect match). this.Match_Distance = 1000; // When deleting a large block of text (over ~64 characters), how close do // the contents have to be to match the expected contents. (0.0 = perfection, // 1.0 = very loose). Note that Match_Threshold controls how closely the // end points of a delete need to match. this.Patch_DeleteThreshold = 0.5; // Chunk size for context length. this.Patch_Margin = 4; // The number of bits in an int. this.Match_MaxBits = 32; } // DIFF FUNCTIONS /** * The data structure representing a diff is an array of tuples: * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] * which means: delete 'Hello', add 'Goodbye' and keep ' world.' */ var DIFF_DELETE = -1; var DIFF_INSERT = 1; var DIFF_EQUAL = 0; /** @typedef {{0: number, 1: string}} */ diff_match_patch.Diff; /** * Find the differences between two texts. Simplifies the problem by stripping * any common prefix or suffix off the texts before diffing. * @param {string} text1 Old string to be diffed. * @param {string} text2 New string to be diffed. * @param {boolean=} opt_checklines Optional speedup flag. If present and false, * then don't run a line-level diff first to identify the changed areas. * Defaults to true, which does a faster, slightly less optimal diff. * @param {number} opt_deadline Optional time when the diff should be complete * by. Used internally for recursive calls. Users should set DiffTimeout * instead. * @return {!Array.} Array of diff tuples. */ diff_match_patch.prototype.diff_main = function(text1, text2, opt_checklines, opt_deadline) { // Set a deadline by which time the diff must be complete. if (typeof opt_deadline == 'undefined') { if (this.Diff_Timeout <= 0) { opt_deadline = Number.MAX_VALUE; } else { opt_deadline = (new Date).getTime() + this.Diff_Timeout * 1000; } } var deadline = opt_deadline; // Check for null inputs. if (text1 == null || text2 == null) { throw new Error('Null input. (diff_main)'); } // Check for equality (speedup). if (text1 == text2) { if (text1) { return [[DIFF_EQUAL, text1]]; } return []; } if (typeof opt_checklines == 'undefined') { opt_checklines = true; } var checklines = opt_checklines; // Trim off common prefix (speedup). var commonlength = this.diff_commonPrefix(text1, text2); var commonprefix = text1.substring(0, commonlength); text1 = text1.substring(commonlength); text2 = text2.substring(commonlength); // Trim off common suffix (speedup). commonlength = this.diff_commonSuffix(text1, text2); var commonsuffix = text1.substring(text1.length - commonlength); text1 = text1.substring(0, text1.length - commonlength); text2 = text2.substring(0, text2.length - commonlength); // Compute the diff on the middle block. var diffs = this.diff_compute_(text1, text2, checklines, deadline); // Restore the prefix and suffix. if (commonprefix) { diffs.unshift([DIFF_EQUAL, commonprefix]); } if (commonsuffix) { diffs.push([DIFF_EQUAL, commonsuffix]); } this.diff_cleanupMerge(diffs); return diffs; }; /** * Find the differences between two texts. Assumes that the texts do not * have any common prefix or suffix. * @param {string} text1 Old string to be diffed. * @param {string} text2 New string to be diffed. * @param {boolean} checklines Speedup flag. If false, then don't run a * line-level diff first to identify the changed areas. * If true, then run a faster, slightly less optimal diff. * @param {number} deadline Time when the diff should be complete by. * @return {!Array.} Array of diff tuples. * @private */ diff_match_patch.prototype.diff_compute_ = function(text1, text2, checklines, deadline) { var diffs; if (!text1) { // Just add some text (speedup). return [[DIFF_INSERT, text2]]; } if (!text2) { // Just delete some text (speedup). return [[DIFF_DELETE, text1]]; } var longtext = text1.length > text2.length ? text1 : text2; var shorttext = text1.length > text2.length ? text2 : text1; var i = longtext.indexOf(shorttext); if (i != -1) { // Shorter text is inside the longer text (speedup). diffs = [[DIFF_INSERT, longtext.substring(0, i)], [DIFF_EQUAL, shorttext], [DIFF_INSERT, longtext.substring(i + shorttext.length)]]; // Swap insertions for deletions if diff is reversed. if (text1.length > text2.length) { diffs[0][0] = diffs[2][0] = DIFF_DELETE; } return diffs; } if (shorttext.length == 1) { // Single character string. // After the previous speedup, the character can't be an equality. return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; } // Check to see if the problem can be split in two. var hm = this.diff_halfMatch_(text1, text2); if (hm) { // A half-match was found, sort out the return data. var text1_a = hm[0]; var text1_b = hm[1]; var text2_a = hm[2]; var text2_b = hm[3]; var mid_common = hm[4]; // Send both pairs off for separate processing. var diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline); var diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline); // Merge the results. return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b); } if (checklines && text1.length > 100 && text2.length > 100) { return this.diff_lineMode_(text1, text2, deadline); } return this.diff_bisect_(text1, text2, deadline); }; /** * Do a quick line-level diff on both strings, then rediff the parts for * greater accuracy. * This speedup can produce non-minimal diffs. * @param {string} text1 Old string to be diffed. * @param {string} text2 New string to be diffed. * @param {number} deadline Time when the diff should be complete by. * @return {!Array.} Array of diff tuples. * @private */ diff_match_patch.prototype.diff_lineMode_ = function(text1, text2, deadline) { // Scan the text on a line-by-line basis first. var a = this.diff_linesToChars_(text1, text2); text1 = a.chars1; text2 = a.chars2; var linearray = a.lineArray; var diffs = this.diff_main(text1, text2, false, deadline); // Convert the diff back to original text. this.diff_charsToLines_(diffs, linearray); // Eliminate freak matches (e.g. blank lines) this.diff_cleanupSemantic(diffs); // Rediff any replacement blocks, this time character-by-character. // Add a dummy entry at the end. diffs.push([DIFF_EQUAL, '']); var pointer = 0; var count_delete = 0; var count_insert = 0; var text_delete = ''; var text_insert = ''; while (pointer < diffs.length) { switch (diffs[pointer][0]) { case DIFF_INSERT: count_insert++; text_insert += diffs[pointer][1]; break; case DIFF_DELETE: count_delete++; text_delete += diffs[pointer][1]; break; case DIFF_EQUAL: // Upon reaching an equality, check for prior redundancies. if (count_delete >= 1 && count_insert >= 1) { // Delete the offending records and add the merged ones. diffs.splice(pointer - count_delete - count_insert, count_delete + count_insert); pointer = pointer - count_delete - count_insert; var a = this.diff_main(text_delete, text_insert, false, deadline); for (var j = a.length - 1; j >= 0; j--) { diffs.splice(pointer, 0, a[j]); } pointer = pointer + a.length; } count_insert = 0; count_delete = 0; text_delete = ''; text_insert = ''; break; } pointer++; } diffs.pop(); // Remove the dummy entry at the end. return diffs; }; /** * Find the 'middle snake' of a diff, split the problem in two * and return the recursively constructed diff. * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. * @param {string} text1 Old string to be diffed. * @param {string} text2 New string to be diffed. * @param {number} deadline Time at which to bail if not yet complete. * @return {!Array.} Array of diff tuples. * @private */ diff_match_patch.prototype.diff_bisect_ = function(text1, text2, deadline) { // Cache the text lengths to prevent multiple calls. var text1_length = text1.length; var text2_length = text2.length; var max_d = Math.ceil((text1_length + text2_length) / 2); var v_offset = max_d; var v_length = 2 * max_d; var v1 = new Array(v_length); var v2 = new Array(v_length); // Setting all elements to -1 is faster in Chrome & Firefox than mixing // integers and undefined. for (var x = 0; x < v_length; x++) { v1[x] = -1; v2[x] = -1; } v1[v_offset + 1] = 0; v2[v_offset + 1] = 0; var delta = text1_length - text2_length; // If the total number of characters is odd, then the front path will collide // with the reverse path. var front = (delta % 2 != 0); // Offsets for start and end of k loop. // Prevents mapping of space beyond the grid. var k1start = 0; var k1end = 0; var k2start = 0; var k2end = 0; for (var d = 0; d < max_d; d++) { // Bail out if deadline is reached. if ((new Date()).getTime() > deadline) { break; } // Walk the front path one step. for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { var k1_offset = v_offset + k1; var x1; if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { x1 = v1[k1_offset + 1]; } else { x1 = v1[k1_offset - 1] + 1; } var y1 = x1 - k1; while (x1 < text1_length && y1 < text2_length && text1.charAt(x1) == text2.charAt(y1)) { x1++; y1++; } v1[k1_offset] = x1; if (x1 > text1_length) { // Ran off the right of the graph. k1end += 2; } else if (y1 > text2_length) { // Ran off the bottom of the graph. k1start += 2; } else if (front) { var k2_offset = v_offset + delta - k1; if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { // Mirror x2 onto top-left coordinate system. var x2 = text1_length - v2[k2_offset]; if (x1 >= x2) { // Overlap detected. return this.diff_bisectSplit_(text1, text2, x1, y1, deadline); } } } } // Walk the reverse path one step. for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { var k2_offset = v_offset + k2; var x2; if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { x2 = v2[k2_offset + 1]; } else { x2 = v2[k2_offset - 1] + 1; } var y2 = x2 - k2; while (x2 < text1_length && y2 < text2_length && text1.charAt(text1_length - x2 - 1) == text2.charAt(text2_length - y2 - 1)) { x2++; y2++; } v2[k2_offset] = x2; if (x2 > text1_length) { // Ran off the left of the graph. k2end += 2; } else if (y2 > text2_length) { // Ran off the top of the graph. k2start += 2; } else if (!front) { var k1_offset = v_offset + delta - k2; if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { var x1 = v1[k1_offset]; var y1 = v_offset + x1 - k1_offset; // Mirror x2 onto top-left coordinate system. x2 = text1_length - x2; if (x1 >= x2) { // Overlap detected. return this.diff_bisectSplit_(text1, text2, x1, y1, deadline); } } } } } // Diff took too long and hit the deadline or // number of diffs equals number of characters, no commonality at all. return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; }; /** * Given the location of the 'middle snake', split the diff in two parts * and recurse. * @param {string} text1 Old string to be diffed. * @param {string} text2 New string to be diffed. * @param {number} x Index of split point in text1. * @param {number} y Index of split point in text2. * @param {number} deadline Time at which to bail if not yet complete. * @return {!Array.} Array of diff tuples. * @private */ diff_match_patch.prototype.diff_bisectSplit_ = function(text1, text2, x, y, deadline) { var text1a = text1.substring(0, x); var text2a = text2.substring(0, y); var text1b = text1.substring(x); var text2b = text2.substring(y); // Compute both diffs serially. var diffs = this.diff_main(text1a, text2a, false, deadline); var diffsb = this.diff_main(text1b, text2b, false, deadline); return diffs.concat(diffsb); }; /** * Split two texts into an array of strings. Reduce the texts to a string of * hashes where each Unicode character represents one line. * @param {string} text1 First string. * @param {string} text2 Second string. * @return {{chars1: string, chars2: string, lineArray: !Array.}} * An object containing the encoded text1, the encoded text2 and * the array of unique strings. * The zeroth element of the array of unique strings is intentionally blank. * @private */ diff_match_patch.prototype.diff_linesToChars_ = function(text1, text2) { var lineArray = []; // e.g. lineArray[4] == 'Hello\n' var lineHash = {}; // e.g. lineHash['Hello\n'] == 4 // '\x00' is a valid character, but various debuggers don't like it. // So we'll insert a junk entry to avoid generating a null character. lineArray[0] = ''; /** * Split a text into an array of strings. Reduce the texts to a string of * hashes where each Unicode character represents one line. * Modifies linearray and linehash through being a closure. * @param {string} text String to encode. * @return {string} Encoded string. * @private */ function diff_linesToCharsMunge_(text) { var chars = ''; // Walk the text, pulling out a substring for each line. // text.split('\n') would would temporarily double our memory footprint. // Modifying text would create many large strings to garbage collect. var lineStart = 0; var lineEnd = -1; // Keeping our own length variable is faster than looking it up. var lineArrayLength = lineArray.length; while (lineEnd < text.length - 1) { lineEnd = text.indexOf('\n', lineStart); if (lineEnd == -1) { lineEnd = text.length - 1; } var line = text.substring(lineStart, lineEnd + 1); lineStart = lineEnd + 1; if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) { chars += String.fromCharCode(lineHash[line]); } else { chars += String.fromCharCode(lineArrayLength); lineHash[line] = lineArrayLength; lineArray[lineArrayLength++] = line; } } return chars; } var chars1 = diff_linesToCharsMunge_(text1); var chars2 = diff_linesToCharsMunge_(text2); return {chars1: chars1, chars2: chars2, lineArray: lineArray}; }; /** * Rehydrate the text in a diff from a string of line hashes to real lines of * text. * @param {!Array.} diffs Array of diff tuples. * @param {!Array.} lineArray Array of unique strings. * @private */ diff_match_patch.prototype.diff_charsToLines_ = function(diffs, lineArray) { for (var x = 0; x < diffs.length; x++) { var chars = diffs[x][1]; var text = []; for (var y = 0; y < chars.length; y++) { text[y] = lineArray[chars.charCodeAt(y)]; } diffs[x][1] = text.join(''); } }; /** * Determine the common prefix of two strings. * @param {string} text1 First string. * @param {string} text2 Second string. * @return {number} The number of characters common to the start of each * string. */ diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) { // Quick check for common null cases. if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) { return 0; } // Binary search. // Performance analysis: http://neil.fraser.name/news/2007/10/09/ var pointermin = 0; var pointermax = Math.min(text1.length, text2.length); var pointermid = pointermax; var pointerstart = 0; while (pointermin < pointermid) { if (text1.substring(pointerstart, pointermid) == text2.substring(pointerstart, pointermid)) { pointermin = pointermid; pointerstart = pointermin; } else { pointermax = pointermid; } pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); } return pointermid; }; /** * Determine the common suffix of two strings. * @param {string} text1 First string. * @param {string} text2 Second string. * @return {number} The number of characters common to the end of each string. */ diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) { // Quick check for common null cases. if (!text1 || !text2 || text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) { return 0; } // Binary search. // Performance analysis: http://neil.fraser.name/news/2007/10/09/ var pointermin = 0; var pointermax = Math.min(text1.length, text2.length); var pointermid = pointermax; var pointerend = 0; while (pointermin < pointermid) { if (text1.substring(text1.length - pointermid, text1.length - pointerend) == text2.substring(text2.length - pointermid, text2.length - pointerend)) { pointermin = pointermid; pointerend = pointermin; } else { pointermax = pointermid; } pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); } return pointermid; }; /** * Determine if the suffix of one string is the prefix of another. * @param {string} text1 First string. * @param {string} text2 Second string. * @return {number} The number of characters common to the end of the first * string and the start of the second string. * @private */ diff_match_patch.prototype.diff_commonOverlap_ = function(text1, text2) { // Cache the text lengths to prevent multiple calls. var text1_length = text1.length; var text2_length = text2.length; // Eliminate the null case. if (text1_length == 0 || text2_length == 0) { return 0; } // Truncate the longer string. if (text1_length > text2_length) { text1 = text1.substring(text1_length - text2_length); } else if (text1_length < text2_length) { text2 = text2.substring(0, text1_length); } var text_length = Math.min(text1_length, text2_length); // Quick check for the worst case. if (text1 == text2) { return text_length; } // Start by looking for a single character match // and increase length until no match is found. // Performance analysis: http://neil.fraser.name/news/2010/11/04/ var best = 0; var length = 1; while (true) { var pattern = text1.substring(text_length - length); var found = text2.indexOf(pattern); if (found == -1) { return best; } length += found; if (found == 0 || text1.substring(text_length - length) == text2.substring(0, length)) { best = length; length++; } } }; /** * Do the two texts share a substring which is at least half the length of the * longer text? * This speedup can produce non-minimal diffs. * @param {string} text1 First string. * @param {string} text2 Second string. * @return {Array.} Five element Array, containing the prefix of * text1, the suffix of text1, the prefix of text2, the suffix of * text2 and the common middle. Or null if there was no match. * @private */ diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) { if (this.Diff_Timeout <= 0) { // Don't risk returning a non-optimal diff if we have unlimited time. return null; } var longtext = text1.length > text2.length ? text1 : text2; var shorttext = text1.length > text2.length ? text2 : text1; if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { return null; // Pointless. } var dmp = this; // 'this' becomes 'window' in a closure. /** * Does a substring of shorttext exist within longtext such that the substring * is at least half the length of longtext? * Closure, but does not reference any external variables. * @param {string} longtext Longer string. * @param {string} shorttext Shorter string. * @param {number} i Start index of quarter length substring within longtext. * @return {Array.} Five element Array, containing the prefix of * longtext, the suffix of longtext, the prefix of shorttext, the suffix * of shorttext and the common middle. Or null if there was no match. * @private */ function diff_halfMatchI_(longtext, shorttext, i) { // Start with a 1/4 length substring at position i as a seed. var seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); var j = -1; var best_common = ''; var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b; while ((j = shorttext.indexOf(seed, j + 1)) != -1) { var prefixLength = dmp.diff_commonPrefix(longtext.substring(i), shorttext.substring(j)); var suffixLength = dmp.diff_commonSuffix(longtext.substring(0, i), shorttext.substring(0, j)); if (best_common.length < suffixLength + prefixLength) { best_common = shorttext.substring(j - suffixLength, j) + shorttext.substring(j, j + prefixLength); best_longtext_a = longtext.substring(0, i - suffixLength); best_longtext_b = longtext.substring(i + prefixLength); best_shorttext_a = shorttext.substring(0, j - suffixLength); best_shorttext_b = shorttext.substring(j + prefixLength); } } if (best_common.length * 2 >= longtext.length) { return [best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b, best_common]; } else { return null; } } // First check if the second quarter is the seed for a half-match. var hm1 = diff_halfMatchI_(longtext, shorttext, Math.ceil(longtext.length / 4)); // Check again based on the third quarter. var hm2 = diff_halfMatchI_(longtext, shorttext, Math.ceil(longtext.length / 2)); var hm; if (!hm1 && !hm2) { return null; } else if (!hm2) { hm = hm1; } else if (!hm1) { hm = hm2; } else { // Both matched. Select the longest. hm = hm1[4].length > hm2[4].length ? hm1 : hm2; } // A half-match was found, sort out the return data. var text1_a, text1_b, text2_a, text2_b; if (text1.length > text2.length) { text1_a = hm[0]; text1_b = hm[1]; text2_a = hm[2]; text2_b = hm[3]; } else { text2_a = hm[0]; text2_b = hm[1]; text1_a = hm[2]; text1_b = hm[3]; } var mid_common = hm[4]; return [text1_a, text1_b, text2_a, text2_b, mid_common]; }; /** * Reduce the number of edits by eliminating semantically trivial equalities. * @param {!Array.} diffs Array of diff tuples. */ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { var changes = false; var equalities = []; // Stack of indices where equalities are found. var equalitiesLength = 0; // Keeping our own length var is faster in JS. /** @type {?string} */ var lastequality = null; // Always equal to diffs[equalities[equalitiesLength - 1]][1] var pointer = 0; // Index of current position. // Number of characters that changed prior to the equality. var length_insertions1 = 0; var length_deletions1 = 0; // Number of characters that changed after the equality. var length_insertions2 = 0; var length_deletions2 = 0; while (pointer < diffs.length) { if (diffs[pointer][0] == DIFF_EQUAL) { // Equality found. equalities[equalitiesLength++] = pointer; length_insertions1 = length_insertions2; length_deletions1 = length_deletions2; length_insertions2 = 0; length_deletions2 = 0; lastequality = diffs[pointer][1]; } else { // An insertion or deletion. if (diffs[pointer][0] == DIFF_INSERT) { length_insertions2 += diffs[pointer][1].length; } else { length_deletions2 += diffs[pointer][1].length; } // Eliminate an equality that is smaller or equal to the edits on both // sides of it. if (lastequality && (lastequality.length <= Math.max(length_insertions1, length_deletions1)) && (lastequality.length <= Math.max(length_insertions2, length_deletions2))) { // Duplicate record. diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]); // Change second copy to insert. diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; // Throw away the equality we just deleted. equalitiesLength--; // Throw away the previous equality (it needs to be reevaluated). equalitiesLength--; pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; length_insertions1 = 0; // Reset the counters. length_deletions1 = 0; length_insertions2 = 0; length_deletions2 = 0; lastequality = null; changes = true; } } pointer++; } // Normalize the diff. if (changes) { this.diff_cleanupMerge(diffs); } this.diff_cleanupSemanticLossless(diffs); // Find any overlaps between deletions and insertions. // e.g: abcxxxxxxdef // -> abcxxxdef // e.g: xxxabcdefxxx // -> defxxxabc // Only extract an overlap if it is as big as the edit ahead or behind it. pointer = 1; while (pointer < diffs.length) { if (diffs[pointer - 1][0] == DIFF_DELETE && diffs[pointer][0] == DIFF_INSERT) { var deletion = diffs[pointer - 1][1]; var insertion = diffs[pointer][1]; var overlap_length1 = this.diff_commonOverlap_(deletion, insertion); var overlap_length2 = this.diff_commonOverlap_(insertion, deletion); if (overlap_length1 >= overlap_length2) { if (overlap_length1 >= deletion.length / 2 || overlap_length1 >= insertion.length / 2) { // Overlap found. Insert an equality and trim the surrounding edits. diffs.splice(pointer, 0, [DIFF_EQUAL, insertion.substring(0, overlap_length1)]); diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlap_length1); diffs[pointer + 1][1] = insertion.substring(overlap_length1); pointer++; } } else { if (overlap_length2 >= deletion.length / 2 || overlap_length2 >= insertion.length / 2) { // Reverse overlap found. // Insert an equality and swap and trim the surrounding edits. diffs.splice(pointer, 0, [DIFF_EQUAL, deletion.substring(0, overlap_length2)]); diffs[pointer - 1][0] = DIFF_INSERT; diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlap_length2); diffs[pointer + 1][0] = DIFF_DELETE; diffs[pointer + 1][1] = deletion.substring(overlap_length2); pointer++; } } pointer++; } pointer++; } }; /** * Look for single edits surrounded on both sides by equalities * which can be shifted sideways to align the edit to a word boundary. * e.g: The cat came. -> The cat came. * @param {!Array.} diffs Array of diff tuples. */ diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) { /** * Given two strings, compute a score representing whether the internal * boundary falls on logical boundaries. * Scores range from 6 (best) to 0 (worst). * Closure, but does not reference any external variables. * @param {string} one First string. * @param {string} two Second string. * @return {number} The score. * @private */ function diff_cleanupSemanticScore_(one, two) { if (!one || !two) { // Edges are the best. return 6; } // Each port of this function behaves slightly differently due to // subtle differences in each language's definition of things like // 'whitespace'. Since this function's purpose is largely cosmetic, // the choice has been made to use each language's native features // rather than force total conformity. var char1 = one.charAt(one.length - 1); var char2 = two.charAt(0); var nonAlphaNumeric1 = char1.match(diff_match_patch.nonAlphaNumericRegex_); var nonAlphaNumeric2 = char2.match(diff_match_patch.nonAlphaNumericRegex_); var whitespace1 = nonAlphaNumeric1 && char1.match(diff_match_patch.whitespaceRegex_); var whitespace2 = nonAlphaNumeric2 && char2.match(diff_match_patch.whitespaceRegex_); var lineBreak1 = whitespace1 && char1.match(diff_match_patch.linebreakRegex_); var lineBreak2 = whitespace2 && char2.match(diff_match_patch.linebreakRegex_); var blankLine1 = lineBreak1 && one.match(diff_match_patch.blanklineEndRegex_); var blankLine2 = lineBreak2 && two.match(diff_match_patch.blanklineStartRegex_); if (blankLine1 || blankLine2) { // Five points for blank lines. return 5; } else if (lineBreak1 || lineBreak2) { // Four points for line breaks. return 4; } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { // Three points for end of sentences. return 3; } else if (whitespace1 || whitespace2) { // Two points for whitespace. return 2; } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { // One point for non-alphanumeric. return 1; } return 0; } var pointer = 1; // Intentionally ignore the first and last element (don't need checking). while (pointer < diffs.length - 1) { if (diffs[pointer - 1][0] == DIFF_EQUAL && diffs[pointer + 1][0] == DIFF_EQUAL) { // This is a single edit surrounded by equalities. var equality1 = diffs[pointer - 1][1]; var edit = diffs[pointer][1]; var equality2 = diffs[pointer + 1][1]; // First, shift the edit as far left as possible. var commonOffset = this.diff_commonSuffix(equality1, edit); if (commonOffset) { var commonString = edit.substring(edit.length - commonOffset); equality1 = equality1.substring(0, equality1.length - commonOffset); edit = commonString + edit.substring(0, edit.length - commonOffset); equality2 = commonString + equality2; } // Second, step character by character right, looking for the best fit. var bestEquality1 = equality1; var bestEdit = edit; var bestEquality2 = equality2; var bestScore = diff_cleanupSemanticScore_(equality1, edit) + diff_cleanupSemanticScore_(edit, equality2); while (edit.charAt(0) === equality2.charAt(0)) { equality1 += edit.charAt(0); edit = edit.substring(1) + equality2.charAt(0); equality2 = equality2.substring(1); var score = diff_cleanupSemanticScore_(equality1, edit) + diff_cleanupSemanticScore_(edit, equality2); // The >= encourages trailing rather than leading whitespace on edits. if (score >= bestScore) { bestScore = score; bestEquality1 = equality1; bestEdit = edit; bestEquality2 = equality2; } } if (diffs[pointer - 1][1] != bestEquality1) { // We have an improvement, save it back to the diff. if (bestEquality1) { diffs[pointer - 1][1] = bestEquality1; } else { diffs.splice(pointer - 1, 1); pointer--; } diffs[pointer][1] = bestEdit; if (bestEquality2) { diffs[pointer + 1][1] = bestEquality2; } else { diffs.splice(pointer + 1, 1); pointer--; } } } pointer++; } }; // Define some regex patterns for matching boundaries. diff_match_patch.nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/; diff_match_patch.whitespaceRegex_ = /\s/; diff_match_patch.linebreakRegex_ = /[\r\n]/; diff_match_patch.blanklineEndRegex_ = /\n\r?\n$/; diff_match_patch.blanklineStartRegex_ = /^\r?\n\r?\n/; /** * Reduce the number of edits by eliminating operationally trivial equalities. * @param {!Array.} diffs Array of diff tuples. */ diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) { var changes = false; var equalities = []; // Stack of indices where equalities are found. var equalitiesLength = 0; // Keeping our own length var is faster in JS. /** @type {?string} */ var lastequality = null; // Always equal to diffs[equalities[equalitiesLength - 1]][1] var pointer = 0; // Index of current position. // Is there an insertion operation before the last equality. var pre_ins = false; // Is there a deletion operation before the last equality. var pre_del = false; // Is there an insertion operation after the last equality. var post_ins = false; // Is there a deletion operation after the last equality. var post_del = false; while (pointer < diffs.length) { if (diffs[pointer][0] == DIFF_EQUAL) { // Equality found. if (diffs[pointer][1].length < this.Diff_EditCost && (post_ins || post_del)) { // Candidate found. equalities[equalitiesLength++] = pointer; pre_ins = post_ins; pre_del = post_del; lastequality = diffs[pointer][1]; } else { // Not a candidate, and can never become one. equalitiesLength = 0; lastequality = null; } post_ins = post_del = false; } else { // An insertion or deletion. if (diffs[pointer][0] == DIFF_DELETE) { post_del = true; } else { post_ins = true; } /* * Five types to be split: * ABXYCD * AXCD * ABXC * AXCD * ABXC */ if (lastequality && ((pre_ins && pre_del && post_ins && post_del) || ((lastequality.length < this.Diff_EditCost / 2) && (pre_ins + pre_del + post_ins + post_del) == 3))) { // Duplicate record. diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]); // Change second copy to insert. diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; equalitiesLength--; // Throw away the equality we just deleted; lastequality = null; if (pre_ins && pre_del) { // No changes made which could affect previous entry, keep going. post_ins = post_del = true; equalitiesLength = 0; } else { equalitiesLength--; // Throw away the previous equality. pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; post_ins = post_del = false; } changes = true; } } pointer++; } if (changes) { this.diff_cleanupMerge(diffs); } }; /** * Reorder and merge like edit sections. Merge equalities. * Any edit section can move as long as it doesn't cross an equality. * @param {!Array.} diffs Array of diff tuples. */ diff_match_patch.prototype.diff_cleanupMerge = function(diffs) { diffs.push([DIFF_EQUAL, '']); // Add a dummy entry at the end. var pointer = 0; var count_delete = 0; var count_insert = 0; var text_delete = ''; var text_insert = ''; var commonlength; while (pointer < diffs.length) { switch (diffs[pointer][0]) { case DIFF_INSERT: count_insert++; text_insert += diffs[pointer][1]; pointer++; break; case DIFF_DELETE: count_delete++; text_delete += diffs[pointer][1]; pointer++; break; case DIFF_EQUAL: // Upon reaching an equality, check for prior redundancies. if (count_delete + count_insert > 1) { if (count_delete !== 0 && count_insert !== 0) { // Factor out any common prefixies. commonlength = this.diff_commonPrefix(text_insert, text_delete); if (commonlength !== 0) { if ((pointer - count_delete - count_insert) > 0 && diffs[pointer - count_delete - count_insert - 1][0] == DIFF_EQUAL) { diffs[pointer - count_delete - count_insert - 1][1] += text_insert.substring(0, commonlength); } else { diffs.splice(0, 0, [DIFF_EQUAL, text_insert.substring(0, commonlength)]); pointer++; } text_insert = text_insert.substring(commonlength); text_delete = text_delete.substring(commonlength); } // Factor out any common suffixies. commonlength = this.diff_commonSuffix(text_insert, text_delete); if (commonlength !== 0) { diffs[pointer][1] = text_insert.substring(text_insert.length - commonlength) + diffs[pointer][1]; text_insert = text_insert.substring(0, text_insert.length - commonlength); text_delete = text_delete.substring(0, text_delete.length - commonlength); } } // Delete the offending records and add the merged ones. if (count_delete === 0) { diffs.splice(pointer - count_insert, count_delete + count_insert, [DIFF_INSERT, text_insert]); } else if (count_insert === 0) { diffs.splice(pointer - count_delete, count_delete + count_insert, [DIFF_DELETE, text_delete]); } else { diffs.splice(pointer - count_delete - count_insert, count_delete + count_insert, [DIFF_DELETE, text_delete], [DIFF_INSERT, text_insert]); } pointer = pointer - count_delete - count_insert + (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1; } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) { // Merge this equality with the previous one. diffs[pointer - 1][1] += diffs[pointer][1]; diffs.splice(pointer, 1); } else { pointer++; } count_insert = 0; count_delete = 0; text_delete = ''; text_insert = ''; break; } } if (diffs[diffs.length - 1][1] === '') { diffs.pop(); // Remove the dummy entry at the end. } // Second pass: look for single edits surrounded on both sides by equalities // which can be shifted sideways to eliminate an equality. // e.g: ABAC -> ABAC var changes = false; pointer = 1; // Intentionally ignore the first and last element (don't need checking). while (pointer < diffs.length - 1) { if (diffs[pointer - 1][0] == DIFF_EQUAL && diffs[pointer + 1][0] == DIFF_EQUAL) { // This is a single edit surrounded by equalities. if (diffs[pointer][1].substring(diffs[pointer][1].length - diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) { // Shift the edit over the previous equality. diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length); diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; diffs.splice(pointer - 1, 1); changes = true; } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) == diffs[pointer + 1][1]) { // Shift the edit over the next equality. diffs[pointer - 1][1] += diffs[pointer + 1][1]; diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1]; diffs.splice(pointer + 1, 1); changes = true; } } pointer++; } // If shifts were made, the diff needs reordering and another shift sweep. if (changes) { this.diff_cleanupMerge(diffs); } }; /** * loc is a location in text1, compute and return the equivalent location in * text2. * e.g. 'The cat' vs 'The big cat', 1->1, 5->8 * @param {!Array.} diffs Array of diff tuples. * @param {number} loc Location within text1. * @return {number} Location within text2. */ diff_match_patch.prototype.diff_xIndex = function(diffs, loc) { var chars1 = 0; var chars2 = 0; var last_chars1 = 0; var last_chars2 = 0; var x; for (x = 0; x < diffs.length; x++) { if (diffs[x][0] !== DIFF_INSERT) { // Equality or deletion. chars1 += diffs[x][1].length; } if (diffs[x][0] !== DIFF_DELETE) { // Equality or insertion. chars2 += diffs[x][1].length; } if (chars1 > loc) { // Overshot the location. break; } last_chars1 = chars1; last_chars2 = chars2; } // Was the location was deleted? if (diffs.length != x && diffs[x][0] === DIFF_DELETE) { return last_chars2; } // Add the remaining character length. return last_chars2 + (loc - last_chars1); }; /** * Convert a diff array into a pretty HTML report. * @param {!Array.} diffs Array of diff tuples. * @return {string} HTML representation. */ diff_match_patch.prototype.diff_prettyHtml = function(diffs) { var html = []; var pattern_amp = /&/g; var pattern_lt = //g; var pattern_para = /\n/g; for (var x = 0; x < diffs.length; x++) { var op = diffs[x][0]; // Operation (insert, delete, equal) var data = diffs[x][1]; // Text of change. var text = data.replace(pattern_amp, '&').replace(pattern_lt, '<') .replace(pattern_gt, '>').replace(pattern_para, '¶
'); switch (op) { case DIFF_INSERT: html[x] = '' + text + ''; break; case DIFF_DELETE: html[x] = '' + text + ''; break; case DIFF_EQUAL: html[x] = '' + text + ''; break; } } return html.join(''); }; /** * Compute and return the source text (all equalities and deletions). * @param {!Array.} diffs Array of diff tuples. * @return {string} Source text. */ diff_match_patch.prototype.diff_text1 = function(diffs) { var text = []; for (var x = 0; x < diffs.length; x++) { if (diffs[x][0] !== DIFF_INSERT) { text[x] = diffs[x][1]; } } return text.join(''); }; /** * Compute and return the destination text (all equalities and insertions). * @param {!Array.} diffs Array of diff tuples. * @return {string} Destination text. */ diff_match_patch.prototype.diff_text2 = function(diffs) { var text = []; for (var x = 0; x < diffs.length; x++) { if (diffs[x][0] !== DIFF_DELETE) { text[x] = diffs[x][1]; } } return text.join(''); }; /** * Compute the Levenshtein distance; the number of inserted, deleted or * substituted characters. * @param {!Array.} diffs Array of diff tuples. * @return {number} Number of changes. */ diff_match_patch.prototype.diff_levenshtein = function(diffs) { var levenshtein = 0; var insertions = 0; var deletions = 0; for (var x = 0; x < diffs.length; x++) { var op = diffs[x][0]; var data = diffs[x][1]; switch (op) { case DIFF_INSERT: insertions += data.length; break; case DIFF_DELETE: deletions += data.length; break; case DIFF_EQUAL: // A deletion and an insertion is one substitution. levenshtein += Math.max(insertions, deletions); insertions = 0; deletions = 0; break; } } levenshtein += Math.max(insertions, deletions); return levenshtein; }; /** * Crush the diff into an encoded string which describes the operations * required to transform text1 into text2. * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. * Operations are tab-separated. Inserted text is escaped using %xx notation. * @param {!Array.} diffs Array of diff tuples. * @return {string} Delta text. */ diff_match_patch.prototype.diff_toDelta = function(diffs) { var text = []; for (var x = 0; x < diffs.length; x++) { switch (diffs[x][0]) { case DIFF_INSERT: text[x] = '+' + encodeURI(diffs[x][1]); break; case DIFF_DELETE: text[x] = '-' + diffs[x][1].length; break; case DIFF_EQUAL: text[x] = '=' + diffs[x][1].length; break; } } return text.join('\t').replace(/%20/g, ' '); }; /** * Given the original text1, and an encoded string which describes the * operations required to transform text1 into text2, compute the full diff. * @param {string} text1 Source string for the diff. * @param {string} delta Delta text. * @return {!Array.} Array of diff tuples. * @throws {!Error} If invalid input. */ diff_match_patch.prototype.diff_fromDelta = function(text1, delta) { var diffs = []; var diffsLength = 0; // Keeping our own length var is faster in JS. var pointer = 0; // Cursor in text1 var tokens = delta.split(/\t/g); for (var x = 0; x < tokens.length; x++) { // Each token begins with a one character parameter which specifies the // operation of this token (delete, insert, equality). var param = tokens[x].substring(1); switch (tokens[x].charAt(0)) { case '+': try { diffs[diffsLength++] = [DIFF_INSERT, decodeURI(param)]; } catch (ex) { // Malformed URI sequence. throw new Error('Illegal escape in diff_fromDelta: ' + param); } break; case '-': // Fall through. case '=': var n = parseInt(param, 10); if (isNaN(n) || n < 0) { throw new Error('Invalid number in diff_fromDelta: ' + param); } var text = text1.substring(pointer, pointer += n); if (tokens[x].charAt(0) == '=') { diffs[diffsLength++] = [DIFF_EQUAL, text]; } else { diffs[diffsLength++] = [DIFF_DELETE, text]; } break; default: // Blank tokens are ok (from a trailing \t). // Anything else is an error. if (tokens[x]) { throw new Error('Invalid diff operation in diff_fromDelta: ' + tokens[x]); } } } if (pointer != text1.length) { throw new Error('Delta length (' + pointer + ') does not equal source text length (' + text1.length + ').'); } return diffs; }; // MATCH FUNCTIONS /** * Locate the best instance of 'pattern' in 'text' near 'loc'. * @param {string} text The text to search. * @param {string} pattern The pattern to search for. * @param {number} loc The location to search around. * @return {number} Best match index or -1. */ diff_match_patch.prototype.match_main = function(text, pattern, loc) { // Check for null inputs. if (text == null || pattern == null || loc == null) { throw new Error('Null input. (match_main)'); } loc = Math.max(0, Math.min(loc, text.length)); if (text == pattern) { // Shortcut (potentially not guaranteed by the algorithm) return 0; } else if (!text.length) { // Nothing to match. return -1; } else if (text.substring(loc, loc + pattern.length) == pattern) { // Perfect match at the perfect spot! (Includes case of null pattern) return loc; } else { // Do a fuzzy compare. return this.match_bitap_(text, pattern, loc); } }; /** * Locate the best instance of 'pattern' in 'text' near 'loc' using the * Bitap algorithm. * @param {string} text The text to search. * @param {string} pattern The pattern to search for. * @param {number} loc The location to search around. * @return {number} Best match index or -1. * @private */ diff_match_patch.prototype.match_bitap_ = function(text, pattern, loc) { if (pattern.length > this.Match_MaxBits) { throw new Error('Pattern too long for this browser.'); } // Initialise the alphabet. var s = this.match_alphabet_(pattern); var dmp = this; // 'this' becomes 'window' in a closure. /** * Compute and return the score for a match with e errors and x location. * Accesses loc and pattern through being a closure. * @param {number} e Number of errors in match. * @param {number} x Location of match. * @return {number} Overall score for match (0.0 = good, 1.0 = bad). * @private */ function match_bitapScore_(e, x) { var accuracy = e / pattern.length; var proximity = Math.abs(loc - x); if (!dmp.Match_Distance) { // Dodge divide by zero error. return proximity ? 1.0 : accuracy; } return accuracy + (proximity / dmp.Match_Distance); } // Highest score beyond which we give up. var score_threshold = this.Match_Threshold; // Is there a nearby exact match? (speedup) var best_loc = text.indexOf(pattern, loc); if (best_loc != -1) { score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold); // What about in the other direction? (speedup) best_loc = text.lastIndexOf(pattern, loc + pattern.length); if (best_loc != -1) { score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold); } } // Initialise the bit arrays. var matchmask = 1 << (pattern.length - 1); best_loc = -1; var bin_min, bin_mid; var bin_max = pattern.length + text.length; var last_rd; for (var d = 0; d < pattern.length; d++) { // Scan for the best match; each iteration allows for one more error. // Run a binary search to determine how far from 'loc' we can stray at this // error level. bin_min = 0; bin_mid = bin_max; while (bin_min < bin_mid) { if (match_bitapScore_(d, loc + bin_mid) <= score_threshold) { bin_min = bin_mid; } else { bin_max = bin_mid; } bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min); } // Use the result from this iteration as the maximum for the next. bin_max = bin_mid; var start = Math.max(1, loc - bin_mid + 1); var finish = Math.min(loc + bin_mid, text.length) + pattern.length; var rd = Array(finish + 2); rd[finish + 1] = (1 << d) - 1; for (var j = finish; j >= start; j--) { // The alphabet (s) is a sparse hash, so the following line generates // warnings. var charMatch = s[text.charAt(j - 1)]; if (d === 0) { // First pass: exact match. rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; } else { // Subsequent passes: fuzzy match. rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1]; } if (rd[j] & matchmask) { var score = match_bitapScore_(d, j - 1); // This match will almost certainly be better than any existing match. // But check anyway. if (score <= score_threshold) { // Told you so. score_threshold = score; best_loc = j - 1; if (best_loc > loc) { // When passing loc, don't exceed our current distance from loc. start = Math.max(1, 2 * loc - best_loc); } else { // Already passed loc, downhill from here on in. break; } } } } // No hope for a (better) match at greater error levels. if (match_bitapScore_(d + 1, loc) > score_threshold) { break; } last_rd = rd; } return best_loc; }; /** * Initialise the alphabet for the Bitap algorithm. * @param {string} pattern The text to encode. * @return {!Object} Hash of character locations. * @private */ diff_match_patch.prototype.match_alphabet_ = function(pattern) { var s = {}; for (var i = 0; i < pattern.length; i++) { s[pattern.charAt(i)] = 0; } for (var i = 0; i < pattern.length; i++) { s[pattern.charAt(i)] |= 1 << (pattern.length - i - 1); } return s; }; // PATCH FUNCTIONS /** * Increase the context until it is unique, * but don't let the pattern expand beyond Match_MaxBits. * @param {!diff_match_patch.patch_obj} patch The patch to grow. * @param {string} text Source text. * @private */ diff_match_patch.prototype.patch_addContext_ = function(patch, text) { if (text.length == 0) { return; } var pattern = text.substring(patch.start2, patch.start2 + patch.length1); var padding = 0; // Look for the first and last matches of pattern in text. If two different // matches are found, increase the pattern length. while (text.indexOf(pattern) != text.lastIndexOf(pattern) && pattern.length < this.Match_MaxBits - this.Patch_Margin - this.Patch_Margin) { padding += this.Patch_Margin; pattern = text.substring(patch.start2 - padding, patch.start2 + patch.length1 + padding); } // Add one chunk for good luck. padding += this.Patch_Margin; // Add the prefix. var prefix = text.substring(patch.start2 - padding, patch.start2); if (prefix) { patch.diffs.unshift([DIFF_EQUAL, prefix]); } // Add the suffix. var suffix = text.substring(patch.start2 + patch.length1, patch.start2 + patch.length1 + padding); if (suffix) { patch.diffs.push([DIFF_EQUAL, suffix]); } // Roll back the start points. patch.start1 -= prefix.length; patch.start2 -= prefix.length; // Extend the lengths. patch.length1 += prefix.length + suffix.length; patch.length2 += prefix.length + suffix.length; }; /** * Compute a list of patches to turn text1 into text2. * Use diffs if provided, otherwise compute it ourselves. * There are four ways to call this function, depending on what data is * available to the caller: * Method 1: * a = text1, b = text2 * Method 2: * a = diffs * Method 3 (optimal): * a = text1, b = diffs * Method 4 (deprecated, use method 3): * a = text1, b = text2, c = diffs * * @param {string|!Array.} a text1 (methods 1,3,4) or * Array of diff tuples for text1 to text2 (method 2). * @param {string|!Array.} opt_b text2 (methods 1,4) or * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2). * @param {string|!Array.} opt_c Array of diff tuples * for text1 to text2 (method 4) or undefined (methods 1,2,3). * @return {!Array.} Array of Patch objects. */ diff_match_patch.prototype.patch_make = function(a, opt_b, opt_c) { var text1, diffs; if (typeof a == 'string' && typeof opt_b == 'string' && typeof opt_c == 'undefined') { // Method 1: text1, text2 // Compute diffs from text1 and text2. text1 = /** @type {string} */(a); diffs = this.diff_main(text1, /** @type {string} */(opt_b), true); if (diffs.length > 2) { this.diff_cleanupSemantic(diffs); this.diff_cleanupEfficiency(diffs); } } else if (a && typeof a == 'object' && typeof opt_b == 'undefined' && typeof opt_c == 'undefined') { // Method 2: diffs // Compute text1 from diffs. diffs = /** @type {!Array.} */(a); text1 = this.diff_text1(diffs); } else if (typeof a == 'string' && opt_b && typeof opt_b == 'object' && typeof opt_c == 'undefined') { // Method 3: text1, diffs text1 = /** @type {string} */(a); diffs = /** @type {!Array.} */(opt_b); } else if (typeof a == 'string' && typeof opt_b == 'string' && opt_c && typeof opt_c == 'object') { // Method 4: text1, text2, diffs // text2 is not used. text1 = /** @type {string} */(a); diffs = /** @type {!Array.} */(opt_c); } else { throw new Error('Unknown call format to patch_make.'); } if (diffs.length === 0) { return []; // Get rid of the null case. } var patches = []; var patch = new diff_match_patch.patch_obj(); var patchDiffLength = 0; // Keeping our own length var is faster in JS. var char_count1 = 0; // Number of characters into the text1 string. var char_count2 = 0; // Number of characters into the text2 string. // Start with text1 (prepatch_text) and apply the diffs until we arrive at // text2 (postpatch_text). We recreate the patches one by one to determine // context info. var prepatch_text = text1; var postpatch_text = text1; for (var x = 0; x < diffs.length; x++) { var diff_type = diffs[x][0]; var diff_text = diffs[x][1]; if (!patchDiffLength && diff_type !== DIFF_EQUAL) { // A new patch starts here. patch.start1 = char_count1; patch.start2 = char_count2; } switch (diff_type) { case DIFF_INSERT: patch.diffs[patchDiffLength++] = diffs[x]; patch.length2 += diff_text.length; postpatch_text = postpatch_text.substring(0, char_count2) + diff_text + postpatch_text.substring(char_count2); break; case DIFF_DELETE: patch.length1 += diff_text.length; patch.diffs[patchDiffLength++] = diffs[x]; postpatch_text = postpatch_text.substring(0, char_count2) + postpatch_text.substring(char_count2 + diff_text.length); break; case DIFF_EQUAL: if (diff_text.length <= 2 * this.Patch_Margin && patchDiffLength && diffs.length != x + 1) { // Small equality inside a patch. patch.diffs[patchDiffLength++] = diffs[x]; patch.length1 += diff_text.length; patch.length2 += diff_text.length; } else if (diff_text.length >= 2 * this.Patch_Margin) { // Time for a new patch. if (patchDiffLength) { this.patch_addContext_(patch, prepatch_text); patches.push(patch); patch = new diff_match_patch.patch_obj(); patchDiffLength = 0; // Unlike Unidiff, our patch lists have a rolling context. // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff // Update prepatch text & pos to reflect the application of the // just completed patch. prepatch_text = postpatch_text; char_count1 = char_count2; } } break; } // Update the current character count. if (diff_type !== DIFF_INSERT) { char_count1 += diff_text.length; } if (diff_type !== DIFF_DELETE) { char_count2 += diff_text.length; } } // Pick up the leftover patch if not empty. if (patchDiffLength) { this.patch_addContext_(patch, prepatch_text); patches.push(patch); } return patches; }; /** * Given an array of patches, return another array that is identical. * @param {!Array.} patches Array of Patch objects. * @return {!Array.} Array of Patch objects. */ diff_match_patch.prototype.patch_deepCopy = function(patches) { // Making deep copies is hard in JavaScript. var patchesCopy = []; for (var x = 0; x < patches.length; x++) { var patch = patches[x]; var patchCopy = new diff_match_patch.patch_obj(); patchCopy.diffs = []; for (var y = 0; y < patch.diffs.length; y++) { patchCopy.diffs[y] = patch.diffs[y].slice(); } patchCopy.start1 = patch.start1; patchCopy.start2 = patch.start2; patchCopy.length1 = patch.length1; patchCopy.length2 = patch.length2; patchesCopy[x] = patchCopy; } return patchesCopy; }; /** * Merge a set of patches onto the text. Return a patched text, as well * as a list of true/false values indicating which patches were applied. * @param {!Array.} patches Array of Patch objects. * @param {string} text Old text. * @return {!Array.>} Two element Array, containing the * new text and an array of boolean values. */ diff_match_patch.prototype.patch_apply = function(patches, text) { if (patches.length == 0) { return [text, []]; } // Deep copy the patches so that no changes are made to originals. patches = this.patch_deepCopy(patches); var nullPadding = this.patch_addPadding(patches); text = nullPadding + text + nullPadding; this.patch_splitMax(patches); // delta keeps track of the offset between the expected and actual location // of the previous patch. If there are patches expected at positions 10 and // 20, but the first patch was found at 12, delta is 2 and the second patch // has an effective expected position of 22. var delta = 0; var results = []; for (var x = 0; x < patches.length; x++) { var expected_loc = patches[x].start2 + delta; var text1 = this.diff_text1(patches[x].diffs); var start_loc; var end_loc = -1; if (text1.length > this.Match_MaxBits) { // patch_splitMax will only provide an oversized pattern in the case of // a monster delete. start_loc = this.match_main(text, text1.substring(0, this.Match_MaxBits), expected_loc); if (start_loc != -1) { end_loc = this.match_main(text, text1.substring(text1.length - this.Match_MaxBits), expected_loc + text1.length - this.Match_MaxBits); if (end_loc == -1 || start_loc >= end_loc) { // Can't find valid trailing context. Drop this patch. start_loc = -1; } } } else { start_loc = this.match_main(text, text1, expected_loc); } if (start_loc == -1) { // No match found. :( results[x] = false; // Subtract the delta for this failed patch from subsequent patches. delta -= patches[x].length2 - patches[x].length1; } else { // Found a match. :) results[x] = true; delta = start_loc - expected_loc; var text2; if (end_loc == -1) { text2 = text.substring(start_loc, start_loc + text1.length); } else { text2 = text.substring(start_loc, end_loc + this.Match_MaxBits); } if (text1 == text2) { // Perfect match, just shove the replacement text in. text = text.substring(0, start_loc) + this.diff_text2(patches[x].diffs) + text.substring(start_loc + text1.length); } else { // Imperfect match. Run a diff to get a framework of equivalent // indices. var diffs = this.diff_main(text1, text2, false); if (text1.length > this.Match_MaxBits && this.diff_levenshtein(diffs) / text1.length > this.Patch_DeleteThreshold) { // The end points match, but the content is unacceptably bad. results[x] = false; } else { this.diff_cleanupSemanticLossless(diffs); var index1 = 0; var index2; for (var y = 0; y < patches[x].diffs.length; y++) { var mod = patches[x].diffs[y]; if (mod[0] !== DIFF_EQUAL) { index2 = this.diff_xIndex(diffs, index1); } if (mod[0] === DIFF_INSERT) { // Insertion text = text.substring(0, start_loc + index2) + mod[1] + text.substring(start_loc + index2); } else if (mod[0] === DIFF_DELETE) { // Deletion text = text.substring(0, start_loc + index2) + text.substring(start_loc + this.diff_xIndex(diffs, index1 + mod[1].length)); } if (mod[0] !== DIFF_DELETE) { index1 += mod[1].length; } } } } } } // Strip the padding off. text = text.substring(nullPadding.length, text.length - nullPadding.length); return [text, results]; }; /** * Add some padding on text start and end so that edges can match something. * Intended to be called only from within patch_apply. * @param {!Array.} patches Array of Patch objects. * @return {string} The padding string added to each side. */ diff_match_patch.prototype.patch_addPadding = function(patches) { var paddingLength = this.Patch_Margin; var nullPadding = ''; for (var x = 1; x <= paddingLength; x++) { nullPadding += String.fromCharCode(x); } // Bump all the patches forward. for (var x = 0; x < patches.length; x++) { patches[x].start1 += paddingLength; patches[x].start2 += paddingLength; } // Add some padding on start of first diff. var patch = patches[0]; var diffs = patch.diffs; if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) { // Add nullPadding equality. diffs.unshift([DIFF_EQUAL, nullPadding]); patch.start1 -= paddingLength; // Should be 0. patch.start2 -= paddingLength; // Should be 0. patch.length1 += paddingLength; patch.length2 += paddingLength; } else if (paddingLength > diffs[0][1].length) { // Grow first equality. var extraLength = paddingLength - diffs[0][1].length; diffs[0][1] = nullPadding.substring(diffs[0][1].length) + diffs[0][1]; patch.start1 -= extraLength; patch.start2 -= extraLength; patch.length1 += extraLength; patch.length2 += extraLength; } // Add some padding on end of last diff. patch = patches[patches.length - 1]; diffs = patch.diffs; if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) { // Add nullPadding equality. diffs.push([DIFF_EQUAL, nullPadding]); patch.length1 += paddingLength; patch.length2 += paddingLength; } else if (paddingLength > diffs[diffs.length - 1][1].length) { // Grow last equality. var extraLength = paddingLength - diffs[diffs.length - 1][1].length; diffs[diffs.length - 1][1] += nullPadding.substring(0, extraLength); patch.length1 += extraLength; patch.length2 += extraLength; } return nullPadding; }; /** * Look through the patches and break up any which are longer than the maximum * limit of the match algorithm. * Intended to be called only from within patch_apply. * @param {!Array.} patches Array of Patch objects. */ diff_match_patch.prototype.patch_splitMax = function(patches) { var patch_size = this.Match_MaxBits; for (var x = 0; x < patches.length; x++) { if (patches[x].length1 <= patch_size) { continue; } var bigpatch = patches[x]; // Remove the big old patch. patches.splice(x--, 1); var start1 = bigpatch.start1; var start2 = bigpatch.start2; var precontext = ''; while (bigpatch.diffs.length !== 0) { // Create one of several smaller patches. var patch = new diff_match_patch.patch_obj(); var empty = true; patch.start1 = start1 - precontext.length; patch.start2 = start2 - precontext.length; if (precontext !== '') { patch.length1 = patch.length2 = precontext.length; patch.diffs.push([DIFF_EQUAL, precontext]); } while (bigpatch.diffs.length !== 0 && patch.length1 < patch_size - this.Patch_Margin) { var diff_type = bigpatch.diffs[0][0]; var diff_text = bigpatch.diffs[0][1]; if (diff_type === DIFF_INSERT) { // Insertions are harmless. patch.length2 += diff_text.length; start2 += diff_text.length; patch.diffs.push(bigpatch.diffs.shift()); empty = false; } else if (diff_type === DIFF_DELETE && patch.diffs.length == 1 && patch.diffs[0][0] == DIFF_EQUAL && diff_text.length > 2 * patch_size) { // This is a large deletion. Let it pass in one chunk. patch.length1 += diff_text.length; start1 += diff_text.length; empty = false; patch.diffs.push([diff_type, diff_text]); bigpatch.diffs.shift(); } else { // Deletion or equality. Only take as much as we can stomach. diff_text = diff_text.substring(0, patch_size - patch.length1 - this.Patch_Margin); patch.length1 += diff_text.length; start1 += diff_text.length; if (diff_type === DIFF_EQUAL) { patch.length2 += diff_text.length; start2 += diff_text.length; } else { empty = false; } patch.diffs.push([diff_type, diff_text]); if (diff_text == bigpatch.diffs[0][1]) { bigpatch.diffs.shift(); } else { bigpatch.diffs[0][1] = bigpatch.diffs[0][1].substring(diff_text.length); } } } // Compute the head context for the next patch. precontext = this.diff_text2(patch.diffs); precontext = precontext.substring(precontext.length - this.Patch_Margin); // Append the end context for this patch. var postcontext = this.diff_text1(bigpatch.diffs) .substring(0, this.Patch_Margin); if (postcontext !== '') { patch.length1 += postcontext.length; patch.length2 += postcontext.length; if (patch.diffs.length !== 0 && patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) { patch.diffs[patch.diffs.length - 1][1] += postcontext; } else { patch.diffs.push([DIFF_EQUAL, postcontext]); } } if (!empty) { patches.splice(++x, 0, patch); } } } }; /** * Take a list of patches and return a textual representation. * @param {!Array.} patches Array of Patch objects. * @return {string} Text representation of patches. */ diff_match_patch.prototype.patch_toText = function(patches) { var text = []; for (var x = 0; x < patches.length; x++) { text[x] = patches[x]; } return text.join(''); }; /** * Parse a textual representation of patches and return a list of Patch objects. * @param {string} textline Text representation of patches. * @return {!Array.} Array of Patch objects. * @throws {!Error} If invalid input. */ diff_match_patch.prototype.patch_fromText = function(textline) { var patches = []; if (!textline) { return patches; } var text = textline.split('\n'); var textPointer = 0; var patchHeader = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/; while (textPointer < text.length) { var m = text[textPointer].match(patchHeader); if (!m) { throw new Error('Invalid patch string: ' + text[textPointer]); } var patch = new diff_match_patch.patch_obj(); patches.push(patch); patch.start1 = parseInt(m[1], 10); if (m[2] === '') { patch.start1--; patch.length1 = 1; } else if (m[2] == '0') { patch.length1 = 0; } else { patch.start1--; patch.length1 = parseInt(m[2], 10); } patch.start2 = parseInt(m[3], 10); if (m[4] === '') { patch.start2--; patch.length2 = 1; } else if (m[4] == '0') { patch.length2 = 0; } else { patch.start2--; patch.length2 = parseInt(m[4], 10); } textPointer++; while (textPointer < text.length) { var sign = text[textPointer].charAt(0); try { var line = decodeURI(text[textPointer].substring(1)); } catch (ex) { // Malformed URI sequence. throw new Error('Illegal escape in patch_fromText: ' + line); } if (sign == '-') { // Deletion. patch.diffs.push([DIFF_DELETE, line]); } else if (sign == '+') { // Insertion. patch.diffs.push([DIFF_INSERT, line]); } else if (sign == ' ') { // Minor equality. patch.diffs.push([DIFF_EQUAL, line]); } else if (sign == '@') { // Start of next patch. break; } else if (sign === '') { // Blank line? Whatever. } else { // WTF? throw new Error('Invalid patch mode "' + sign + '" in: ' + line); } textPointer++; } } return patches; }; /** * Class representing one patch operation. * @constructor */ diff_match_patch.patch_obj = function() { /** @type {!Array.} */ this.diffs = []; /** @type {?number} */ this.start1 = null; /** @type {?number} */ this.start2 = null; /** @type {number} */ this.length1 = 0; /** @type {number} */ this.length2 = 0; }; /** * Emmulate GNU diff's format. * Header: @@ -382,8 +481,9 @@ * Indicies are printed as 1-based, not 0-based. * @return {string} The GNU diff string. */ diff_match_patch.patch_obj.prototype.toString = function() { var coords1, coords2; if (this.length1 === 0) { coords1 = this.start1 + ',0'; } else if (this.length1 == 1) { coords1 = this.start1 + 1; } else { coords1 = (this.start1 + 1) + ',' + this.length1; } if (this.length2 === 0) { coords2 = this.start2 + ',0'; } else if (this.length2 == 1) { coords2 = this.start2 + 1; } else { coords2 = (this.start2 + 1) + ',' + this.length2; } var text = ['@@ -' + coords1 + ' +' + coords2 + ' @@\n']; var op; // Escape the body of the patch with %xx notation. for (var x = 0; x < this.diffs.length; x++) { switch (this.diffs[x][0]) { case DIFF_INSERT: op = '+'; break; case DIFF_DELETE: op = '-'; break; case DIFF_EQUAL: op = ' '; break; } text[x + 1] = op + encodeURI(this.diffs[x][1]) + '\n'; } return text.join('').replace(/%20/g, ' '); }; // Export these global variables so that they survive Google's JS compiler. // In a browser, 'this' will be 'window'. // Users of node.js should 'require' the uncompressed version since Google's // JS compiler may break the following exports for non-browser environments. this['diff_match_patch'] = diff_match_patch; this['DIFF_DELETE'] = DIFF_DELETE; this['DIFF_INSERT'] = DIFF_INSERT; this['DIFF_EQUAL'] = DIFF_EQUAL; ================================================ FILE: web/client/resources/js/lib/jquery-ui.js ================================================ /*! jQuery UI - v1.10.4 - 2014-01-17 * http://jqueryui.com * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js, jquery.ui.menu.js, jquery.ui.progressbar.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.slider.js, jquery.ui.sortable.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js * Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ (function( $, undefined ) { var uuid = 0, runiqueId = /^ui-id-\d+$/; // $.ui might exist from components with no dependencies, e.g., $.ui.position $.ui = $.ui || {}; $.extend( $.ui, { version: "1.10.4", keyCode: { BACKSPACE: 8, COMMA: 188, DELETE: 46, DOWN: 40, END: 35, ENTER: 13, ESCAPE: 27, HOME: 36, LEFT: 37, NUMPAD_ADD: 107, NUMPAD_DECIMAL: 110, NUMPAD_DIVIDE: 111, NUMPAD_ENTER: 108, NUMPAD_MULTIPLY: 106, NUMPAD_SUBTRACT: 109, PAGE_DOWN: 34, PAGE_UP: 33, PERIOD: 190, RIGHT: 39, SPACE: 32, TAB: 9, UP: 38 } }); // plugins $.fn.extend({ focus: (function( orig ) { return function( delay, fn ) { return typeof delay === "number" ? this.each(function() { var elem = this; setTimeout(function() { $( elem ).focus(); if ( fn ) { fn.call( elem ); } }, delay ); }) : orig.apply( this, arguments ); }; })( $.fn.focus ), scrollParent: function() { var scrollParent; if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) { scrollParent = this.parents().filter(function() { return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); }).eq(0); } else { scrollParent = this.parents().filter(function() { return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); }).eq(0); } return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent; }, zIndex: function( zIndex ) { if ( zIndex !== undefined ) { return this.css( "zIndex", zIndex ); } if ( this.length ) { var elem = $( this[ 0 ] ), position, value; while ( elem.length && elem[ 0 ] !== document ) { // Ignore z-index if position is set to a value where z-index is ignored by the browser // This makes behavior of this function consistent across browsers // WebKit always returns auto if the element is positioned position = elem.css( "position" ); if ( position === "absolute" || position === "relative" || position === "fixed" ) { // IE returns 0 when zIndex is not specified // other browsers return a string // we ignore the case of nested elements with an explicit value of 0 //
value = parseInt( elem.css( "zIndex" ), 10 ); if ( !isNaN( value ) && value !== 0 ) { return value; } } elem = elem.parent(); } } return 0; }, uniqueId: function() { return this.each(function() { if ( !this.id ) { this.id = "ui-id-" + (++uuid); } }); }, removeUniqueId: function() { return this.each(function() { if ( runiqueId.test( this.id ) ) { $( this ).removeAttr( "id" ); } }); } }); // selectors function focusable( element, isTabIndexNotNaN ) { var map, mapName, img, nodeName = element.nodeName.toLowerCase(); if ( "area" === nodeName ) { map = element.parentNode; mapName = map.name; if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { return false; } img = $( "img[usemap=#" + mapName + "]" )[0]; return !!img && visible( img ); } return ( /input|select|textarea|button|object/.test( nodeName ) ? !element.disabled : "a" === nodeName ? element.href || isTabIndexNotNaN : isTabIndexNotNaN) && // the element and all of its ancestors must be visible visible( element ); } function visible( element ) { return $.expr.filters.visible( element ) && !$( element ).parents().addBack().filter(function() { return $.css( this, "visibility" ) === "hidden"; }).length; } $.extend( $.expr[ ":" ], { data: $.expr.createPseudo ? $.expr.createPseudo(function( dataName ) { return function( elem ) { return !!$.data( elem, dataName ); }; }) : // support: jQuery <1.8 function( elem, i, match ) { return !!$.data( elem, match[ 3 ] ); }, focusable: function( element ) { return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); }, tabbable: function( element ) { var tabIndex = $.attr( element, "tabindex" ), isTabIndexNaN = isNaN( tabIndex ); return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); } }); // support: jQuery <1.8 if ( !$( "" ).outerWidth( 1 ).jquery ) { $.each( [ "Width", "Height" ], function( i, name ) { var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], type = name.toLowerCase(), orig = { innerWidth: $.fn.innerWidth, innerHeight: $.fn.innerHeight, outerWidth: $.fn.outerWidth, outerHeight: $.fn.outerHeight }; function reduce( elem, size, border, margin ) { $.each( side, function() { size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; if ( border ) { size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; } if ( margin ) { size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; } }); return size; } $.fn[ "inner" + name ] = function( size ) { if ( size === undefined ) { return orig[ "inner" + name ].call( this ); } return this.each(function() { $( this ).css( type, reduce( this, size ) + "px" ); }); }; $.fn[ "outer" + name] = function( size, margin ) { if ( typeof size !== "number" ) { return orig[ "outer" + name ].call( this, size ); } return this.each(function() { $( this).css( type, reduce( this, size, true, margin ) + "px" ); }); }; }); } // support: jQuery <1.8 if ( !$.fn.addBack ) { $.fn.addBack = function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); }; } // support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) if ( $( "" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { $.fn.removeData = (function( removeData ) { return function( key ) { if ( arguments.length ) { return removeData.call( this, $.camelCase( key ) ); } else { return removeData.call( this ); } }; })( $.fn.removeData ); } // deprecated $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); $.support.selectstart = "onselectstart" in document.createElement( "div" ); $.fn.extend({ disableSelection: function() { return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + ".ui-disableSelection", function( event ) { event.preventDefault(); }); }, enableSelection: function() { return this.unbind( ".ui-disableSelection" ); } }); $.extend( $.ui, { // $.ui.plugin is deprecated. Use $.widget() extensions instead. plugin: { add: function( module, option, set ) { var i, proto = $.ui[ module ].prototype; for ( i in set ) { proto.plugins[ i ] = proto.plugins[ i ] || []; proto.plugins[ i ].push( [ option, set[ i ] ] ); } }, call: function( instance, name, args ) { var i, set = instance.plugins[ name ]; if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) { return; } for ( i = 0; i < set.length; i++ ) { if ( instance.options[ set[ i ][ 0 ] ] ) { set[ i ][ 1 ].apply( instance.element, args ); } } } }, // only used by resizable hasScroll: function( el, a ) { //If overflow is hidden, the element might have extra content, but the user wants to hide it if ( $( el ).css( "overflow" ) === "hidden") { return false; } var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", has = false; if ( el[ scroll ] > 0 ) { return true; } // TODO: determine which cases actually cause this to happen // if the element doesn't have the scroll set, see if it's possible to // set the scroll el[ scroll ] = 1; has = ( el[ scroll ] > 0 ); el[ scroll ] = 0; return has; } }); })( jQuery ); (function( $, undefined ) { var uuid = 0, slice = Array.prototype.slice, _cleanData = $.cleanData; $.cleanData = function( elems ) { for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { try { $( elem ).triggerHandler( "remove" ); // http://bugs.jquery.com/ticket/8235 } catch( e ) {} } _cleanData( elems ); }; $.widget = function( name, base, prototype ) { var fullName, existingConstructor, constructor, basePrototype, // proxiedPrototype allows the provided prototype to remain unmodified // so that it can be used as a mixin for multiple widgets (#8876) proxiedPrototype = {}, namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; fullName = namespace + "-" + name; if ( !prototype ) { prototype = base; base = $.Widget; } // create selector for plugin $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; existingConstructor = $[ namespace ][ name ]; constructor = $[ namespace ][ name ] = function( options, element ) { // allow instantiation without "new" keyword if ( !this._createWidget ) { return new constructor( options, element ); } // allow instantiation without initializing for simple inheritance // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; // extend with the existing constructor to carry over any static properties $.extend( constructor, existingConstructor, { version: prototype.version, // copy the object used to create the prototype in case we need to // redefine the widget later _proto: $.extend( {}, prototype ), // track widgets that inherit from this widget in case this widget is // redefined after a widget inherits from it _childConstructors: [] }); basePrototype = new base(); // we need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { if ( !$.isFunction( value ) ) { proxiedPrototype[ prop ] = value; return; } proxiedPrototype[ prop ] = (function() { var _super = function() { return base.prototype[ prop ].apply( this, arguments ); }, _superApply = function( args ) { return base.prototype[ prop ].apply( this, args ); }; return function() { var __super = this._super, __superApply = this._superApply, returnValue; this._super = _super; this._superApply = _superApply; returnValue = value.apply( this, arguments ); this._super = __super; this._superApply = __superApply; return returnValue; }; })(); }); constructor.prototype = $.widget.extend( basePrototype, { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name }, proxiedPrototype, { constructor: constructor, namespace: namespace, widgetName: name, widgetFullName: fullName }); // If this widget is being redefined then we need to find all widgets that // are inheriting from it and redefine all of them so that they inherit from // the new version of this widget. We're essentially trying to replace one // level in the prototype chain. if ( existingConstructor ) { $.each( existingConstructor._childConstructors, function( i, child ) { var childPrototype = child.prototype; // redefine the child widget using the same prototype that was // originally used, but inherit from the new version of the base $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); }); // remove the list of existing child constructors from the old constructor // so the old child constructors can be garbage collected delete existingConstructor._childConstructors; } else { base._childConstructors.push( constructor ); } $.widget.bridge( name, constructor ); }; $.widget.extend = function( target ) { var input = slice.call( arguments, 1 ), inputIndex = 0, inputLength = input.length, key, value; for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { target[ key ] = $.isPlainObject( target[ key ] ) ? $.widget.extend( {}, target[ key ], value ) : // Don't extend strings, arrays, etc. with objects $.widget.extend( {}, value ); // Copy everything else by reference } else { target[ key ] = value; } } } } return target; }; $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", args = slice.call( arguments, 1 ), returnValue = this; // allow multiple hashes to be passed on init options = !isMethodCall && args.length ? $.widget.extend.apply( null, [ options ].concat(args) ) : options; if ( isMethodCall ) { this.each(function() { var methodValue, instance = $.data( this, fullName ); if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? returnValue.pushStack( methodValue.get() ) : methodValue; return false; } }); } else { this.each(function() { var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} )._init(); } else { $.data( this, fullName, new object( options, this ) ); } }); } return returnValue; }; }; $.Widget = function( /* options, element */ ) {}; $.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "
", options: { disabled: false, // callbacks create: null }, _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); this.uuid = uuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); this.bindings = $(); this.hoverable = $(); this.focusable = $(); if ( element !== this ) { $.data( element, this.widgetFullName, this ); this._on( true, this.element, { remove: function( event ) { if ( event.target === element ) { this.destroy(); } } }); this.document = $( element.style ? // element within the document element.ownerDocument : // element is window or document element.document || element ); this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); } this._create(); this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, _getCreateOptions: $.noop, _getCreateEventData: $.noop, _create: $.noop, _init: $.noop, destroy: function() { this._destroy(); // we can probably remove the unbind calls in 2.0 // all event bindings should go through this._on() this.element .unbind( this.eventNamespace ) // 1.9 BC for #7810 // TODO remove dual storage .removeData( this.widgetName ) .removeData( this.widgetFullName ) // support: jquery <1.6.3 // http://bugs.jquery.com/ticket/9413 .removeData( $.camelCase( this.widgetFullName ) ); this.widget() .unbind( this.eventNamespace ) .removeAttr( "aria-disabled" ) .removeClass( this.widgetFullName + "-disabled " + "ui-state-disabled" ); // clean up events and states this.bindings.unbind( this.eventNamespace ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); }, _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { var options = key, parts, curOption, i; if ( arguments.length === 0 ) { // don't return a reference to the internal hash return $.widget.extend( {}, this.options ); } if ( typeof key === "string" ) { // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; parts = key.split( "." ); key = parts.shift(); if ( parts.length ) { curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); for ( i = 0; i < parts.length - 1; i++ ) { curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; curOption = curOption[ parts[ i ] ]; } key = parts.pop(); if ( arguments.length === 1 ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { if ( arguments.length === 1 ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; } } this._setOptions( options ); return this; }, _setOptions: function( options ) { var key; for ( key in options ) { this._setOption( key, options[ key ] ); } return this; }, _setOption: function( key, value ) { this.options[ key ] = value; if ( key === "disabled" ) { this.widget() .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) .attr( "aria-disabled", value ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); } return this; }, enable: function() { return this._setOption( "disabled", false ); }, disable: function() { return this._setOption( "disabled", true ); }, _on: function( suppressDisabledCheck, element, handlers ) { var delegateElement, instance = this; // no suppressDisabledCheck flag, shuffle arguments if ( typeof suppressDisabledCheck !== "boolean" ) { handlers = element; element = suppressDisabledCheck; suppressDisabledCheck = false; } // no element argument, shuffle and use this.element if ( !handlers ) { handlers = element; element = this.element; delegateElement = this.widget(); } else { // accept selectors, DOM elements element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } $.each( handlers, function( event, handler ) { function handlerProxy() { // allow widgets to customize the disabled handling // - disabled as an array instead of boolean // - disabled class as method for disabling individual parts if ( !suppressDisabledCheck && ( instance.options.disabled === true || $( this ).hasClass( "ui-state-disabled" ) ) ) { return; } return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } // copy the guid so direct unbinding works if ( typeof handler !== "string" ) { handlerProxy.guid = handler.guid = handler.guid || handlerProxy.guid || $.guid++; } var match = event.match( /^(\w+)\s*(.*)$/ ), eventName = match[1] + instance.eventNamespace, selector = match[2]; if ( selector ) { delegateElement.delegate( selector, eventName, handlerProxy ); } else { element.bind( eventName, handlerProxy ); } }); }, _off: function( element, eventName ) { eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; element.unbind( eventName ).undelegate( eventName ); }, _delay: function( handler, delay ) { function handlerProxy() { return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } var instance = this; return setTimeout( handlerProxy, delay || 0 ); }, _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._on( element, { mouseenter: function( event ) { $( event.currentTarget ).addClass( "ui-state-hover" ); }, mouseleave: function( event ) { $( event.currentTarget ).removeClass( "ui-state-hover" ); } }); }, _focusable: function( element ) { this.focusable = this.focusable.add( element ); this._on( element, { focusin: function( event ) { $( event.currentTarget ).addClass( "ui-state-focus" ); }, focusout: function( event ) { $( event.currentTarget ).removeClass( "ui-state-focus" ); } }); }, _trigger: function( type, event, data ) { var prop, orig, callback = this.options[ type ]; data = data || {}; event = $.Event( event ); event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // the original event may come from any element // so we need to reset the target on the new event event.target = this.element[ 0 ]; // copy original event properties over to the new event orig = event.originalEvent; if ( orig ) { for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } } this.element.trigger( event, data ); return !( $.isFunction( callback ) && callback.apply( this.element[0], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } }; $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { if ( typeof options === "string" ) { options = { effect: options }; } var hasOptions, effectName = !options ? method : options === true || typeof options === "number" ? defaultEffect : options.effect || defaultEffect; options = options || {}; if ( typeof options === "number" ) { options = { duration: options }; } hasOptions = !$.isEmptyObject( options ); options.complete = callback; if ( options.delay ) { element.delay( options.delay ); } if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { element[ method ]( options ); } else if ( effectName !== method && element[ effectName ] ) { element[ effectName ]( options.duration, options.easing, callback ); } else { element.queue(function( next ) { $( this )[ method ](); if ( callback ) { callback.call( element[ 0 ] ); } next(); }); } }; }); })( jQuery ); (function( $, undefined ) { var mouseHandled = false; $( document ).mouseup( function() { mouseHandled = false; }); $.widget("ui.mouse", { version: "1.10.4", options: { cancel: "input,textarea,button,select,option", distance: 1, delay: 0 }, _mouseInit: function() { var that = this; this.element .bind("mousedown."+this.widgetName, function(event) { return that._mouseDown(event); }) .bind("click."+this.widgetName, function(event) { if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { $.removeData(event.target, that.widgetName + ".preventClickEvent"); event.stopImmediatePropagation(); return false; } }); this.started = false; }, // TODO: make sure destroying one instance of mouse doesn't mess with // other instances of mouse _mouseDestroy: function() { this.element.unbind("."+this.widgetName); if ( this._mouseMoveDelegate ) { $(document) .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); } }, _mouseDown: function(event) { // don't let more than one widget handle mouseStart if( mouseHandled ) { return; } // we may have missed mouseup (out of window) (this._mouseStarted && this._mouseUp(event)); this._mouseDownEvent = event; var that = this, btnIsLeft = (event.which === 1), // event.target.nodeName works around a bug in IE 8 with // disabled inputs (#7620) elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { return true; } this.mouseDelayMet = !this.options.delay; if (!this.mouseDelayMet) { this._mouseDelayTimer = setTimeout(function() { that.mouseDelayMet = true; }, this.options.delay); } if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { this._mouseStarted = (this._mouseStart(event) !== false); if (!this._mouseStarted) { event.preventDefault(); return true; } } // Click event may never have fired (Gecko & Opera) if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) { $.removeData(event.target, this.widgetName + ".preventClickEvent"); } // these delegates are required to keep context this._mouseMoveDelegate = function(event) { return that._mouseMove(event); }; this._mouseUpDelegate = function(event) { return that._mouseUp(event); }; $(document) .bind("mousemove."+this.widgetName, this._mouseMoveDelegate) .bind("mouseup."+this.widgetName, this._mouseUpDelegate); event.preventDefault(); mouseHandled = true; return true; }, _mouseMove: function(event) { // IE mouseup check - mouseup happened when mouse was out of window if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { return this._mouseUp(event); } if (this._mouseStarted) { this._mouseDrag(event); return event.preventDefault(); } if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { this._mouseStarted = (this._mouseStart(this._mouseDownEvent, event) !== false); (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); } return !this._mouseStarted; }, _mouseUp: function(event) { $(document) .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); if (this._mouseStarted) { this._mouseStarted = false; if (event.target === this._mouseDownEvent.target) { $.data(event.target, this.widgetName + ".preventClickEvent", true); } this._mouseStop(event); } return false; }, _mouseDistanceMet: function(event) { return (Math.max( Math.abs(this._mouseDownEvent.pageX - event.pageX), Math.abs(this._mouseDownEvent.pageY - event.pageY) ) >= this.options.distance ); }, _mouseDelayMet: function(/* event */) { return this.mouseDelayMet; }, // These are placeholder methods, to be overriden by extending plugin _mouseStart: function(/* event */) {}, _mouseDrag: function(/* event */) {}, _mouseStop: function(/* event */) {}, _mouseCapture: function(/* event */) { return true; } }); })(jQuery); (function( $, undefined ) { $.ui = $.ui || {}; var cachedScrollbarWidth, max = Math.max, abs = Math.abs, round = Math.round, rhorizontal = /left|center|right/, rvertical = /top|center|bottom/, roffset = /[\+\-]\d+(\.[\d]+)?%?/, rposition = /^\w+/, rpercent = /%$/, _position = $.fn.position; function getOffsets( offsets, width, height ) { return [ parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) ]; } function parseCss( element, property ) { return parseInt( $.css( element, property ), 10 ) || 0; } function getDimensions( elem ) { var raw = elem[0]; if ( raw.nodeType === 9 ) { return { width: elem.width(), height: elem.height(), offset: { top: 0, left: 0 } }; } if ( $.isWindow( raw ) ) { return { width: elem.width(), height: elem.height(), offset: { top: elem.scrollTop(), left: elem.scrollLeft() } }; } if ( raw.preventDefault ) { return { width: 0, height: 0, offset: { top: raw.pageY, left: raw.pageX } }; } return { width: elem.outerWidth(), height: elem.outerHeight(), offset: elem.offset() }; } $.position = { scrollbarWidth: function() { if ( cachedScrollbarWidth !== undefined ) { return cachedScrollbarWidth; } var w1, w2, div = $( "
" ), innerDiv = div.children()[0]; $( "body" ).append( div ); w1 = innerDiv.offsetWidth; div.css( "overflow", "scroll" ); w2 = innerDiv.offsetWidth; if ( w1 === w2 ) { w2 = div[0].clientWidth; } div.remove(); return (cachedScrollbarWidth = w1 - w2); }, getScrollInfo: function( within ) { var overflowX = within.isWindow || within.isDocument ? "" : within.element.css( "overflow-x" ), overflowY = within.isWindow || within.isDocument ? "" : within.element.css( "overflow-y" ), hasOverflowX = overflowX === "scroll" || ( overflowX === "auto" && within.width < within.element[0].scrollWidth ), hasOverflowY = overflowY === "scroll" || ( overflowY === "auto" && within.height < within.element[0].scrollHeight ); return { width: hasOverflowY ? $.position.scrollbarWidth() : 0, height: hasOverflowX ? $.position.scrollbarWidth() : 0 }; }, getWithinInfo: function( element ) { var withinElement = $( element || window ), isWindow = $.isWindow( withinElement[0] ), isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9; return { element: withinElement, isWindow: isWindow, isDocument: isDocument, offset: withinElement.offset() || { left: 0, top: 0 }, scrollLeft: withinElement.scrollLeft(), scrollTop: withinElement.scrollTop(), width: isWindow ? withinElement.width() : withinElement.outerWidth(), height: isWindow ? withinElement.height() : withinElement.outerHeight() }; } }; $.fn.position = function( options ) { if ( !options || !options.of ) { return _position.apply( this, arguments ); } // make a copy, we don't want to modify arguments options = $.extend( {}, options ); var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, target = $( options.of ), within = $.position.getWithinInfo( options.within ), scrollInfo = $.position.getScrollInfo( within ), collision = ( options.collision || "flip" ).split( " " ), offsets = {}; dimensions = getDimensions( target ); if ( target[0].preventDefault ) { // force left top to allow flipping options.at = "left top"; } targetWidth = dimensions.width; targetHeight = dimensions.height; targetOffset = dimensions.offset; // clone to reuse original targetOffset later basePosition = $.extend( {}, targetOffset ); // force my and at to have valid horizontal and vertical positions // if a value is missing or invalid, it will be converted to center $.each( [ "my", "at" ], function() { var pos = ( options[ this ] || "" ).split( " " ), horizontalOffset, verticalOffset; if ( pos.length === 1) { pos = rhorizontal.test( pos[ 0 ] ) ? pos.concat( [ "center" ] ) : rvertical.test( pos[ 0 ] ) ? [ "center" ].concat( pos ) : [ "center", "center" ]; } pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; // calculate offsets horizontalOffset = roffset.exec( pos[ 0 ] ); verticalOffset = roffset.exec( pos[ 1 ] ); offsets[ this ] = [ horizontalOffset ? horizontalOffset[ 0 ] : 0, verticalOffset ? verticalOffset[ 0 ] : 0 ]; // reduce to just the positions without the offsets options[ this ] = [ rposition.exec( pos[ 0 ] )[ 0 ], rposition.exec( pos[ 1 ] )[ 0 ] ]; }); // normalize collision option if ( collision.length === 1 ) { collision[ 1 ] = collision[ 0 ]; } if ( options.at[ 0 ] === "right" ) { basePosition.left += targetWidth; } else if ( options.at[ 0 ] === "center" ) { basePosition.left += targetWidth / 2; } if ( options.at[ 1 ] === "bottom" ) { basePosition.top += targetHeight; } else if ( options.at[ 1 ] === "center" ) { basePosition.top += targetHeight / 2; } atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); basePosition.left += atOffset[ 0 ]; basePosition.top += atOffset[ 1 ]; return this.each(function() { var collisionPosition, using, elem = $( this ), elemWidth = elem.outerWidth(), elemHeight = elem.outerHeight(), marginLeft = parseCss( this, "marginLeft" ), marginTop = parseCss( this, "marginTop" ), collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, position = $.extend( {}, basePosition ), myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); if ( options.my[ 0 ] === "right" ) { position.left -= elemWidth; } else if ( options.my[ 0 ] === "center" ) { position.left -= elemWidth / 2; } if ( options.my[ 1 ] === "bottom" ) { position.top -= elemHeight; } else if ( options.my[ 1 ] === "center" ) { position.top -= elemHeight / 2; } position.left += myOffset[ 0 ]; position.top += myOffset[ 1 ]; // if the browser doesn't support fractions, then round for consistent results if ( !$.support.offsetFractions ) { position.left = round( position.left ); position.top = round( position.top ); } collisionPosition = { marginLeft: marginLeft, marginTop: marginTop }; $.each( [ "left", "top" ], function( i, dir ) { if ( $.ui.position[ collision[ i ] ] ) { $.ui.position[ collision[ i ] ][ dir ]( position, { targetWidth: targetWidth, targetHeight: targetHeight, elemWidth: elemWidth, elemHeight: elemHeight, collisionPosition: collisionPosition, collisionWidth: collisionWidth, collisionHeight: collisionHeight, offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], my: options.my, at: options.at, within: within, elem : elem }); } }); if ( options.using ) { // adds feedback as second argument to using callback, if present using = function( props ) { var left = targetOffset.left - position.left, right = left + targetWidth - elemWidth, top = targetOffset.top - position.top, bottom = top + targetHeight - elemHeight, feedback = { target: { element: target, left: targetOffset.left, top: targetOffset.top, width: targetWidth, height: targetHeight }, element: { element: elem, left: position.left, top: position.top, width: elemWidth, height: elemHeight }, horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" }; if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { feedback.horizontal = "center"; } if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { feedback.vertical = "middle"; } if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { feedback.important = "horizontal"; } else { feedback.important = "vertical"; } options.using.call( this, props, feedback ); }; } elem.offset( $.extend( position, { using: using } ) ); }); }; $.ui.position = { fit: { left: function( position, data ) { var within = data.within, withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, outerWidth = within.width, collisionPosLeft = position.left - data.collisionPosition.marginLeft, overLeft = withinOffset - collisionPosLeft, overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, newOverRight; // element is wider than within if ( data.collisionWidth > outerWidth ) { // element is initially over the left side of within if ( overLeft > 0 && overRight <= 0 ) { newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; position.left += overLeft - newOverRight; // element is initially over right side of within } else if ( overRight > 0 && overLeft <= 0 ) { position.left = withinOffset; // element is initially over both left and right sides of within } else { if ( overLeft > overRight ) { position.left = withinOffset + outerWidth - data.collisionWidth; } else { position.left = withinOffset; } } // too far left -> align with left edge } else if ( overLeft > 0 ) { position.left += overLeft; // too far right -> align with right edge } else if ( overRight > 0 ) { position.left -= overRight; // adjust based on position and margin } else { position.left = max( position.left - collisionPosLeft, position.left ); } }, top: function( position, data ) { var within = data.within, withinOffset = within.isWindow ? within.scrollTop : within.offset.top, outerHeight = data.within.height, collisionPosTop = position.top - data.collisionPosition.marginTop, overTop = withinOffset - collisionPosTop, overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, newOverBottom; // element is taller than within if ( data.collisionHeight > outerHeight ) { // element is initially over the top of within if ( overTop > 0 && overBottom <= 0 ) { newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; position.top += overTop - newOverBottom; // element is initially over bottom of within } else if ( overBottom > 0 && overTop <= 0 ) { position.top = withinOffset; // element is initially over both top and bottom of within } else { if ( overTop > overBottom ) { position.top = withinOffset + outerHeight - data.collisionHeight; } else { position.top = withinOffset; } } // too far up -> align with top } else if ( overTop > 0 ) { position.top += overTop; // too far down -> align with bottom edge } else if ( overBottom > 0 ) { position.top -= overBottom; // adjust based on position and margin } else { position.top = max( position.top - collisionPosTop, position.top ); } } }, flip: { left: function( position, data ) { var within = data.within, withinOffset = within.offset.left + within.scrollLeft, outerWidth = within.width, offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, collisionPosLeft = position.left - data.collisionPosition.marginLeft, overLeft = collisionPosLeft - offsetLeft, overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, myOffset = data.my[ 0 ] === "left" ? -data.elemWidth : data.my[ 0 ] === "right" ? data.elemWidth : 0, atOffset = data.at[ 0 ] === "left" ? data.targetWidth : data.at[ 0 ] === "right" ? -data.targetWidth : 0, offset = -2 * data.offset[ 0 ], newOverRight, newOverLeft; if ( overLeft < 0 ) { newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { position.left += myOffset + atOffset + offset; } } else if ( overRight > 0 ) { newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { position.left += myOffset + atOffset + offset; } } }, top: function( position, data ) { var within = data.within, withinOffset = within.offset.top + within.scrollTop, outerHeight = within.height, offsetTop = within.isWindow ? within.scrollTop : within.offset.top, collisionPosTop = position.top - data.collisionPosition.marginTop, overTop = collisionPosTop - offsetTop, overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, top = data.my[ 1 ] === "top", myOffset = top ? -data.elemHeight : data.my[ 1 ] === "bottom" ? data.elemHeight : 0, atOffset = data.at[ 1 ] === "top" ? data.targetHeight : data.at[ 1 ] === "bottom" ? -data.targetHeight : 0, offset = -2 * data.offset[ 1 ], newOverTop, newOverBottom; if ( overTop < 0 ) { newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) { position.top += myOffset + atOffset + offset; } } else if ( overBottom > 0 ) { newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) { position.top += myOffset + atOffset + offset; } } } }, flipfit: { left: function() { $.ui.position.flip.left.apply( this, arguments ); $.ui.position.fit.left.apply( this, arguments ); }, top: function() { $.ui.position.flip.top.apply( this, arguments ); $.ui.position.fit.top.apply( this, arguments ); } } }; // fraction support test (function () { var testElement, testElementParent, testElementStyle, offsetLeft, i, body = document.getElementsByTagName( "body" )[ 0 ], div = document.createElement( "div" ); //Create a "fake body" for testing based on method used in jQuery.support testElement = document.createElement( body ? "div" : "body" ); testElementStyle = { visibility: "hidden", width: 0, height: 0, border: 0, margin: 0, background: "none" }; if ( body ) { $.extend( testElementStyle, { position: "absolute", left: "-1000px", top: "-1000px" }); } for ( i in testElementStyle ) { testElement.style[ i ] = testElementStyle[ i ]; } testElement.appendChild( div ); testElementParent = body || document.documentElement; testElementParent.insertBefore( testElement, testElementParent.firstChild ); div.style.cssText = "position: absolute; left: 10.7432222px;"; offsetLeft = $( div ).offset().left; $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11; testElement.innerHTML = ""; testElementParent.removeChild( testElement ); })(); }( jQuery ) ); (function( $, undefined ) { var uid = 0, hideProps = {}, showProps = {}; hideProps.height = hideProps.paddingTop = hideProps.paddingBottom = hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide"; showProps.height = showProps.paddingTop = showProps.paddingBottom = showProps.borderTopWidth = showProps.borderBottomWidth = "show"; $.widget( "ui.accordion", { version: "1.10.4", options: { active: 0, animate: {}, collapsible: false, event: "click", header: "> li > :first-child,> :not(li):even", heightStyle: "auto", icons: { activeHeader: "ui-icon-triangle-1-s", header: "ui-icon-triangle-1-e" }, // callbacks activate: null, beforeActivate: null }, _create: function() { var options = this.options; this.prevShow = this.prevHide = $(); this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ) // ARIA .attr( "role", "tablist" ); // don't allow collapsible: false and active: false / null if ( !options.collapsible && (options.active === false || options.active == null) ) { options.active = 0; } this._processPanels(); // handle negative values if ( options.active < 0 ) { options.active += this.headers.length; } this._refresh(); }, _getCreateEventData: function() { return { header: this.active, panel: !this.active.length ? $() : this.active.next(), content: !this.active.length ? $() : this.active.next() }; }, _createIcons: function() { var icons = this.options.icons; if ( icons ) { $( "" ) .addClass( "ui-accordion-header-icon ui-icon " + icons.header ) .prependTo( this.headers ); this.active.children( ".ui-accordion-header-icon" ) .removeClass( icons.header ) .addClass( icons.activeHeader ); this.headers.addClass( "ui-accordion-icons" ); } }, _destroyIcons: function() { this.headers .removeClass( "ui-accordion-icons" ) .children( ".ui-accordion-header-icon" ) .remove(); }, _destroy: function() { var contents; // clean up main element this.element .removeClass( "ui-accordion ui-widget ui-helper-reset" ) .removeAttr( "role" ); // clean up headers this.headers .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) .removeAttr( "role" ) .removeAttr( "aria-expanded" ) .removeAttr( "aria-selected" ) .removeAttr( "aria-controls" ) .removeAttr( "tabIndex" ) .each(function() { if ( /^ui-accordion/.test( this.id ) ) { this.removeAttribute( "id" ); } }); this._destroyIcons(); // clean up content panels contents = this.headers.next() .css( "display", "" ) .removeAttr( "role" ) .removeAttr( "aria-hidden" ) .removeAttr( "aria-labelledby" ) .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" ) .each(function() { if ( /^ui-accordion/.test( this.id ) ) { this.removeAttribute( "id" ); } }); if ( this.options.heightStyle !== "content" ) { contents.css( "height", "" ); } }, _setOption: function( key, value ) { if ( key === "active" ) { // _activate() will handle invalid values and update this.options this._activate( value ); return; } if ( key === "event" ) { if ( this.options.event ) { this._off( this.headers, this.options.event ); } this._setupEvents( value ); } this._super( key, value ); // setting collapsible: false while collapsed; open first panel if ( key === "collapsible" && !value && this.options.active === false ) { this._activate( 0 ); } if ( key === "icons" ) { this._destroyIcons(); if ( value ) { this._createIcons(); } } // #5332 - opacity doesn't cascade to positioned elements in IE // so we need to add the disabled class to the headers and panels if ( key === "disabled" ) { this.headers.add( this.headers.next() ) .toggleClass( "ui-state-disabled", !!value ); } }, _keydown: function( event ) { if ( event.altKey || event.ctrlKey ) { return; } var keyCode = $.ui.keyCode, length = this.headers.length, currentIndex = this.headers.index( event.target ), toFocus = false; switch ( event.keyCode ) { case keyCode.RIGHT: case keyCode.DOWN: toFocus = this.headers[ ( currentIndex + 1 ) % length ]; break; case keyCode.LEFT: case keyCode.UP: toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; break; case keyCode.SPACE: case keyCode.ENTER: this._eventHandler( event ); break; case keyCode.HOME: toFocus = this.headers[ 0 ]; break; case keyCode.END: toFocus = this.headers[ length - 1 ]; break; } if ( toFocus ) { $( event.target ).attr( "tabIndex", -1 ); $( toFocus ).attr( "tabIndex", 0 ); toFocus.focus(); event.preventDefault(); } }, _panelKeyDown : function( event ) { if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { $( event.currentTarget ).prev().focus(); } }, refresh: function() { var options = this.options; this._processPanels(); // was collapsed or no panel if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { options.active = false; this.active = $(); // active false only when collapsible is true } else if ( options.active === false ) { this._activate( 0 ); // was active, but active panel is gone } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { // all remaining panel are disabled if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) { options.active = false; this.active = $(); // activate previous panel } else { this._activate( Math.max( 0, options.active - 1 ) ); } // was active, active panel still exists } else { // make sure active index is correct options.active = this.headers.index( this.active ); } this._destroyIcons(); this._refresh(); }, _processPanels: function() { this.headers = this.element.find( this.options.header ) .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" ); this.headers.next() .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ) .filter(":not(.ui-accordion-content-active)") .hide(); }, _refresh: function() { var maxHeight, options = this.options, heightStyle = options.heightStyle, parent = this.element.parent(), accordionId = this.accordionId = "ui-accordion-" + (this.element.attr( "id" ) || ++uid); this.active = this._findActive( options.active ) .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ) .removeClass( "ui-corner-all" ); this.active.next() .addClass( "ui-accordion-content-active" ) .show(); this.headers .attr( "role", "tab" ) .each(function( i ) { var header = $( this ), headerId = header.attr( "id" ), panel = header.next(), panelId = panel.attr( "id" ); if ( !headerId ) { headerId = accordionId + "-header-" + i; header.attr( "id", headerId ); } if ( !panelId ) { panelId = accordionId + "-panel-" + i; panel.attr( "id", panelId ); } header.attr( "aria-controls", panelId ); panel.attr( "aria-labelledby", headerId ); }) .next() .attr( "role", "tabpanel" ); this.headers .not( this.active ) .attr({ "aria-selected": "false", "aria-expanded": "false", tabIndex: -1 }) .next() .attr({ "aria-hidden": "true" }) .hide(); // make sure at least one header is in the tab order if ( !this.active.length ) { this.headers.eq( 0 ).attr( "tabIndex", 0 ); } else { this.active.attr({ "aria-selected": "true", "aria-expanded": "true", tabIndex: 0 }) .next() .attr({ "aria-hidden": "false" }); } this._createIcons(); this._setupEvents( options.event ); if ( heightStyle === "fill" ) { maxHeight = parent.height(); this.element.siblings( ":visible" ).each(function() { var elem = $( this ), position = elem.css( "position" ); if ( position === "absolute" || position === "fixed" ) { return; } maxHeight -= elem.outerHeight( true ); }); this.headers.each(function() { maxHeight -= $( this ).outerHeight( true ); }); this.headers.next() .each(function() { $( this ).height( Math.max( 0, maxHeight - $( this ).innerHeight() + $( this ).height() ) ); }) .css( "overflow", "auto" ); } else if ( heightStyle === "auto" ) { maxHeight = 0; this.headers.next() .each(function() { maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); }) .height( maxHeight ); } }, _activate: function( index ) { var active = this._findActive( index )[ 0 ]; // trying to activate the already active panel if ( active === this.active[ 0 ] ) { return; } // trying to collapse, simulate a click on the currently active header active = active || this.active[ 0 ]; this._eventHandler({ target: active, currentTarget: active, preventDefault: $.noop }); }, _findActive: function( selector ) { return typeof selector === "number" ? this.headers.eq( selector ) : $(); }, _setupEvents: function( event ) { var events = { keydown: "_keydown" }; if ( event ) { $.each( event.split(" "), function( index, eventName ) { events[ eventName ] = "_eventHandler"; }); } this._off( this.headers.add( this.headers.next() ) ); this._on( this.headers, events ); this._on( this.headers.next(), { keydown: "_panelKeyDown" }); this._hoverable( this.headers ); this._focusable( this.headers ); }, _eventHandler: function( event ) { var options = this.options, active = this.active, clicked = $( event.currentTarget ), clickedIsActive = clicked[ 0 ] === active[ 0 ], collapsing = clickedIsActive && options.collapsible, toShow = collapsing ? $() : clicked.next(), toHide = active.next(), eventData = { oldHeader: active, oldPanel: toHide, newHeader: collapsing ? $() : clicked, newPanel: toShow }; event.preventDefault(); if ( // click on active header, but not collapsible ( clickedIsActive && !options.collapsible ) || // allow canceling activation ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { return; } options.active = collapsing ? false : this.headers.index( clicked ); // when the call to ._toggle() comes after the class changes // it causes a very odd bug in IE 8 (see #6720) this.active = clickedIsActive ? $() : clicked; this._toggle( eventData ); // switch classes // corner classes on the previously active header stay after the animation active.removeClass( "ui-accordion-header-active ui-state-active" ); if ( options.icons ) { active.children( ".ui-accordion-header-icon" ) .removeClass( options.icons.activeHeader ) .addClass( options.icons.header ); } if ( !clickedIsActive ) { clicked .removeClass( "ui-corner-all" ) .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ); if ( options.icons ) { clicked.children( ".ui-accordion-header-icon" ) .removeClass( options.icons.header ) .addClass( options.icons.activeHeader ); } clicked .next() .addClass( "ui-accordion-content-active" ); } }, _toggle: function( data ) { var toShow = data.newPanel, toHide = this.prevShow.length ? this.prevShow : data.oldPanel; // handle activating a panel during the animation for another activation this.prevShow.add( this.prevHide ).stop( true, true ); this.prevShow = toShow; this.prevHide = toHide; if ( this.options.animate ) { this._animate( toShow, toHide, data ); } else { toHide.hide(); toShow.show(); this._toggleComplete( data ); } toHide.attr({ "aria-hidden": "true" }); toHide.prev().attr( "aria-selected", "false" ); // if we're switching panels, remove the old header from the tab order // if we're opening from collapsed state, remove the previous header from the tab order // if we're collapsing, then keep the collapsing header in the tab order if ( toShow.length && toHide.length ) { toHide.prev().attr({ "tabIndex": -1, "aria-expanded": "false" }); } else if ( toShow.length ) { this.headers.filter(function() { return $( this ).attr( "tabIndex" ) === 0; }) .attr( "tabIndex", -1 ); } toShow .attr( "aria-hidden", "false" ) .prev() .attr({ "aria-selected": "true", tabIndex: 0, "aria-expanded": "true" }); }, _animate: function( toShow, toHide, data ) { var total, easing, duration, that = this, adjust = 0, down = toShow.length && ( !toHide.length || ( toShow.index() < toHide.index() ) ), animate = this.options.animate || {}, options = down && animate.down || animate, complete = function() { that._toggleComplete( data ); }; if ( typeof options === "number" ) { duration = options; } if ( typeof options === "string" ) { easing = options; } // fall back from options to animation in case of partial down settings easing = easing || options.easing || animate.easing; duration = duration || options.duration || animate.duration; if ( !toHide.length ) { return toShow.animate( showProps, duration, easing, complete ); } if ( !toShow.length ) { return toHide.animate( hideProps, duration, easing, complete ); } total = toShow.show().outerHeight(); toHide.animate( hideProps, { duration: duration, easing: easing, step: function( now, fx ) { fx.now = Math.round( now ); } }); toShow .hide() .animate( showProps, { duration: duration, easing: easing, complete: complete, step: function( now, fx ) { fx.now = Math.round( now ); if ( fx.prop !== "height" ) { adjust += fx.now; } else if ( that.options.heightStyle !== "content" ) { fx.now = Math.round( total - toHide.outerHeight() - adjust ); adjust = 0; } } }); }, _toggleComplete: function( data ) { var toHide = data.oldPanel; toHide .removeClass( "ui-accordion-content-active" ) .prev() .removeClass( "ui-corner-top" ) .addClass( "ui-corner-all" ); // Work around for rendering bug in IE (#5421) if ( toHide.length ) { toHide.parent()[0].className = toHide.parent()[0].className; } this._trigger( "activate", null, data ); } }); })( jQuery ); (function( $, undefined ) { $.widget( "ui.autocomplete", { version: "1.10.4", defaultElement: "", options: { appendTo: null, autoFocus: false, delay: 300, minLength: 1, position: { my: "left top", at: "left bottom", collision: "none" }, source: null, // callbacks change: null, close: null, focus: null, open: null, response: null, search: null, select: null }, requestIndex: 0, pending: 0, _create: function() { // Some browsers only repeat keydown events, not keypress events, // so we use the suppressKeyPress flag to determine if we've already // handled the keydown event. #7269 // Unfortunately the code for & in keypress is the same as the up arrow, // so we use the suppressKeyPressRepeat flag to avoid handling keypress // events when we know the keydown event was used to modify the // search term. #7799 var suppressKeyPress, suppressKeyPressRepeat, suppressInput, nodeName = this.element[0].nodeName.toLowerCase(), isTextarea = nodeName === "textarea", isInput = nodeName === "input"; this.isMultiLine = // Textareas are always multi-line isTextarea ? true : // Inputs are always single-line, even if inside a contentEditable element // IE also treats inputs as contentEditable isInput ? false : // All other element types are determined by whether or not they're contentEditable this.element.prop( "isContentEditable" ); this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; this.isNewMenu = true; this.element .addClass( "ui-autocomplete-input" ) .attr( "autocomplete", "off" ); this._on( this.element, { keydown: function( event ) { if ( this.element.prop( "readOnly" ) ) { suppressKeyPress = true; suppressInput = true; suppressKeyPressRepeat = true; return; } suppressKeyPress = false; suppressInput = false; suppressKeyPressRepeat = false; var keyCode = $.ui.keyCode; switch( event.keyCode ) { case keyCode.PAGE_UP: suppressKeyPress = true; this._move( "previousPage", event ); break; case keyCode.PAGE_DOWN: suppressKeyPress = true; this._move( "nextPage", event ); break; case keyCode.UP: suppressKeyPress = true; this._keyEvent( "previous", event ); break; case keyCode.DOWN: suppressKeyPress = true; this._keyEvent( "next", event ); break; case keyCode.ENTER: case keyCode.NUMPAD_ENTER: // when menu is open and has focus if ( this.menu.active ) { // #6055 - Opera still allows the keypress to occur // which causes forms to submit suppressKeyPress = true; event.preventDefault(); this.menu.select( event ); } break; case keyCode.TAB: if ( this.menu.active ) { this.menu.select( event ); } break; case keyCode.ESCAPE: if ( this.menu.element.is( ":visible" ) ) { this._value( this.term ); this.close( event ); // Different browsers have different default behavior for escape // Single press can mean undo or clear // Double press in IE means clear the whole form event.preventDefault(); } break; default: suppressKeyPressRepeat = true; // search timeout should be triggered before the input value is changed this._searchTimeout( event ); break; } }, keypress: function( event ) { if ( suppressKeyPress ) { suppressKeyPress = false; if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { event.preventDefault(); } return; } if ( suppressKeyPressRepeat ) { return; } // replicate some key handlers to allow them to repeat in Firefox and Opera var keyCode = $.ui.keyCode; switch( event.keyCode ) { case keyCode.PAGE_UP: this._move( "previousPage", event ); break; case keyCode.PAGE_DOWN: this._move( "nextPage", event ); break; case keyCode.UP: this._keyEvent( "previous", event ); break; case keyCode.DOWN: this._keyEvent( "next", event ); break; } }, input: function( event ) { if ( suppressInput ) { suppressInput = false; event.preventDefault(); return; } this._searchTimeout( event ); }, focus: function() { this.selectedItem = null; this.previous = this._value(); }, blur: function( event ) { if ( this.cancelBlur ) { delete this.cancelBlur; return; } clearTimeout( this.searching ); this.close( event ); this._change( event ); } }); this._initSource(); this.menu = $( "
    " ) .addClass( "ui-autocomplete ui-front" ) .appendTo( this._appendTo() ) .menu({ // disable ARIA support, the live region takes care of that role: null }) .hide() .data( "ui-menu" ); this._on( this.menu.element, { mousedown: function( event ) { // prevent moving focus out of the text field event.preventDefault(); // IE doesn't prevent moving focus even with event.preventDefault() // so we set a flag to know when we should ignore the blur event this.cancelBlur = true; this._delay(function() { delete this.cancelBlur; }); // clicking on the scrollbar causes focus to shift to the body // but we can't detect a mouseup or a click immediately afterward // so we have to track the next mousedown and close the menu if // the user clicks somewhere outside of the autocomplete var menuElement = this.menu.element[ 0 ]; if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { this._delay(function() { var that = this; this.document.one( "mousedown", function( event ) { if ( event.target !== that.element[ 0 ] && event.target !== menuElement && !$.contains( menuElement, event.target ) ) { that.close(); } }); }); } }, menufocus: function( event, ui ) { // support: Firefox // Prevent accidental activation of menu items in Firefox (#7024 #9118) if ( this.isNewMenu ) { this.isNewMenu = false; if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { this.menu.blur(); this.document.one( "mousemove", function() { $( event.target ).trigger( event.originalEvent ); }); return; } } var item = ui.item.data( "ui-autocomplete-item" ); if ( false !== this._trigger( "focus", event, { item: item } ) ) { // use value to match what will end up in the input, if it was a key event if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { this._value( item.value ); } } else { // Normally the input is populated with the item's value as the // menu is navigated, causing screen readers to notice a change and // announce the item. Since the focus event was canceled, this doesn't // happen, so we update the live region so that screen readers can // still notice the change and announce it. this.liveRegion.text( item.value ); } }, menuselect: function( event, ui ) { var item = ui.item.data( "ui-autocomplete-item" ), previous = this.previous; // only trigger when focus was lost (click on menu) if ( this.element[0] !== this.document[0].activeElement ) { this.element.focus(); this.previous = previous; // #6109 - IE triggers two focus events and the second // is asynchronous, so we need to reset the previous // term synchronously and asynchronously :-( this._delay(function() { this.previous = previous; this.selectedItem = item; }); } if ( false !== this._trigger( "select", event, { item: item } ) ) { this._value( item.value ); } // reset the term after the select event // this allows custom select handling to work properly this.term = this._value(); this.close( event ); this.selectedItem = item; } }); this.liveRegion = $( "", { role: "status", "aria-live": "polite" }) .addClass( "ui-helper-hidden-accessible" ) .insertBefore( this.element ); // turning off autocomplete prevents the browser from remembering the // value when navigating through history, so we re-enable autocomplete // if the page is unloaded before the widget is destroyed. #7790 this._on( this.window, { beforeunload: function() { this.element.removeAttr( "autocomplete" ); } }); }, _destroy: function() { clearTimeout( this.searching ); this.element .removeClass( "ui-autocomplete-input" ) .removeAttr( "autocomplete" ); this.menu.element.remove(); this.liveRegion.remove(); }, _setOption: function( key, value ) { this._super( key, value ); if ( key === "source" ) { this._initSource(); } if ( key === "appendTo" ) { this.menu.element.appendTo( this._appendTo() ); } if ( key === "disabled" && value && this.xhr ) { this.xhr.abort(); } }, _appendTo: function() { var element = this.options.appendTo; if ( element ) { element = element.jquery || element.nodeType ? $( element ) : this.document.find( element ).eq( 0 ); } if ( !element ) { element = this.element.closest( ".ui-front" ); } if ( !element.length ) { element = this.document[0].body; } return element; }, _initSource: function() { var array, url, that = this; if ( $.isArray(this.options.source) ) { array = this.options.source; this.source = function( request, response ) { response( $.ui.autocomplete.filter( array, request.term ) ); }; } else if ( typeof this.options.source === "string" ) { url = this.options.source; this.source = function( request, response ) { if ( that.xhr ) { that.xhr.abort(); } that.xhr = $.ajax({ url: url, data: request, dataType: "json", success: function( data ) { response( data ); }, error: function() { response( [] ); } }); }; } else { this.source = this.options.source; } }, _searchTimeout: function( event ) { clearTimeout( this.searching ); this.searching = this._delay(function() { // only search if the value has changed if ( this.term !== this._value() ) { this.selectedItem = null; this.search( null, event ); } }, this.options.delay ); }, search: function( value, event ) { value = value != null ? value : this._value(); // always save the actual value, not the one passed as an argument this.term = this._value(); if ( value.length < this.options.minLength ) { return this.close( event ); } if ( this._trigger( "search", event ) === false ) { return; } return this._search( value ); }, _search: function( value ) { this.pending++; this.element.addClass( "ui-autocomplete-loading" ); this.cancelSearch = false; this.source( { term: value }, this._response() ); }, _response: function() { var index = ++this.requestIndex; return $.proxy(function( content ) { if ( index === this.requestIndex ) { this.__response( content ); } this.pending--; if ( !this.pending ) { this.element.removeClass( "ui-autocomplete-loading" ); } }, this ); }, __response: function( content ) { if ( content ) { content = this._normalize( content ); } this._trigger( "response", null, { content: content } ); if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { this._suggest( content ); this._trigger( "open" ); } else { // use ._close() instead of .close() so we don't cancel future searches this._close(); } }, close: function( event ) { this.cancelSearch = true; this._close( event ); }, _close: function( event ) { if ( this.menu.element.is( ":visible" ) ) { this.menu.element.hide(); this.menu.blur(); this.isNewMenu = true; this._trigger( "close", event ); } }, _change: function( event ) { if ( this.previous !== this._value() ) { this._trigger( "change", event, { item: this.selectedItem } ); } }, _normalize: function( items ) { // assume all items have the right format when the first item is complete if ( items.length && items[0].label && items[0].value ) { return items; } return $.map( items, function( item ) { if ( typeof item === "string" ) { return { label: item, value: item }; } return $.extend({ label: item.label || item.value, value: item.value || item.label }, item ); }); }, _suggest: function( items ) { var ul = this.menu.element.empty(); this._renderMenu( ul, items ); this.isNewMenu = true; this.menu.refresh(); // size and position menu ul.show(); this._resizeMenu(); ul.position( $.extend({ of: this.element }, this.options.position )); if ( this.options.autoFocus ) { this.menu.next(); } }, _resizeMenu: function() { var ul = this.menu.element; ul.outerWidth( Math.max( // Firefox wraps long text (possibly a rounding bug) // so we add 1px to avoid the wrapping (#7513) ul.width( "" ).outerWidth() + 1, this.element.outerWidth() ) ); }, _renderMenu: function( ul, items ) { var that = this; $.each( items, function( index, item ) { that._renderItemData( ul, item ); }); }, _renderItemData: function( ul, item ) { return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); }, _renderItem: function( ul, item ) { return $( "
  • " ) .append( $( "" ).text( item.label ) ) .appendTo( ul ); }, _move: function( direction, event ) { if ( !this.menu.element.is( ":visible" ) ) { this.search( null, event ); return; } if ( this.menu.isFirstItem() && /^previous/.test( direction ) || this.menu.isLastItem() && /^next/.test( direction ) ) { this._value( this.term ); this.menu.blur(); return; } this.menu[ direction ]( event ); }, widget: function() { return this.menu.element; }, _value: function() { return this.valueMethod.apply( this.element, arguments ); }, _keyEvent: function( keyEvent, event ) { if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { this._move( keyEvent, event ); // prevents moving cursor to beginning/end of the text field in some browsers event.preventDefault(); } } }); $.extend( $.ui.autocomplete, { escapeRegex: function( value ) { return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); }, filter: function(array, term) { var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); return $.grep( array, function(value) { return matcher.test( value.label || value.value || value ); }); } }); // live region extension, adding a `messages` option // NOTE: This is an experimental API. We are still investigating // a full solution for string manipulation and internationalization. $.widget( "ui.autocomplete", $.ui.autocomplete, { options: { messages: { noResults: "No search results.", results: function( amount ) { return amount + ( amount > 1 ? " results are" : " result is" ) + " available, use up and down arrow keys to navigate."; } } }, __response: function( content ) { var message; this._superApply( arguments ); if ( this.options.disabled || this.cancelSearch ) { return; } if ( content && content.length ) { message = this.options.messages.results( content.length ); } else { message = this.options.messages.noResults; } this.liveRegion.text( message ); } }); }( jQuery )); (function( $, undefined ) { var lastActive, baseClasses = "ui-button ui-widget ui-state-default ui-corner-all", typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only", formResetHandler = function() { var form = $( this ); setTimeout(function() { form.find( ":ui-button" ).button( "refresh" ); }, 1 ); }, radioGroup = function( radio ) { var name = radio.name, form = radio.form, radios = $( [] ); if ( name ) { name = name.replace( /'/g, "\\'" ); if ( form ) { radios = $( form ).find( "[name='" + name + "']" ); } else { radios = $( "[name='" + name + "']", radio.ownerDocument ) .filter(function() { return !this.form; }); } } return radios; }; $.widget( "ui.button", { version: "1.10.4", defaultElement: "").addClass(this._triggerClass). html(!buttonImage ? buttonText : $("").attr( { src:buttonImage, alt:buttonText, title:buttonText }))); input[isRTL ? "before" : "after"](inst.trigger); inst.trigger.click(function() { if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) { $.datepicker._hideDatepicker(); } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) { $.datepicker._hideDatepicker(); $.datepicker._showDatepicker(input[0]); } else { $.datepicker._showDatepicker(input[0]); } return false; }); } }, /* Apply the maximum length for the date format. */ _autoSize: function(inst) { if (this._get(inst, "autoSize") && !inst.inline) { var findMax, max, maxI, i, date = new Date(2009, 12 - 1, 20), // Ensure double digits dateFormat = this._get(inst, "dateFormat"); if (dateFormat.match(/[DM]/)) { findMax = function(names) { max = 0; maxI = 0; for (i = 0; i < names.length; i++) { if (names[i].length > max) { max = names[i].length; maxI = i; } } return maxI; }; date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ? "monthNames" : "monthNamesShort")))); date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ? "dayNames" : "dayNamesShort"))) + 20 - date.getDay()); } inst.input.attr("size", this._formatDate(inst, date).length); } }, /* Attach an inline date picker to a div. */ _inlineDatepicker: function(target, inst) { var divSpan = $(target); if (divSpan.hasClass(this.markerClassName)) { return; } divSpan.addClass(this.markerClassName).append(inst.dpDiv); $.data(target, PROP_NAME, inst); this._setDate(inst, this._getDefaultDate(inst), true); this._updateDatepicker(inst); this._updateAlternate(inst); //If disabled option is true, disable the datepicker before showing it (see ticket #5665) if( inst.settings.disabled ) { this._disableDatepicker( target ); } // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height inst.dpDiv.css( "display", "block" ); }, /* Pop-up the date picker in a "dialog" box. * @param input element - ignored * @param date string or Date - the initial date to display * @param onSelect function - the function to call when a date is selected * @param settings object - update the dialog date picker instance's settings (anonymous object) * @param pos int[2] - coordinates for the dialog's position within the screen or * event - with x/y coordinates or * leave empty for default (screen centre) * @return the manager object */ _dialogDatepicker: function(input, date, onSelect, settings, pos) { var id, browserWidth, browserHeight, scrollX, scrollY, inst = this._dialogInst; // internal instance if (!inst) { this.uuid += 1; id = "dp" + this.uuid; this._dialogInput = $(""); this._dialogInput.keydown(this._doKeyDown); $("body").append(this._dialogInput); inst = this._dialogInst = this._newInst(this._dialogInput, false); inst.settings = {}; $.data(this._dialogInput[0], PROP_NAME, inst); } extendRemove(inst.settings, settings || {}); date = (date && date.constructor === Date ? this._formatDate(inst, date) : date); this._dialogInput.val(date); this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); if (!this._pos) { browserWidth = document.documentElement.clientWidth; browserHeight = document.documentElement.clientHeight; scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; scrollY = document.documentElement.scrollTop || document.body.scrollTop; this._pos = // should use actual width/height below [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; } // move input on screen for focus, but hidden behind dialog this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px"); inst.settings.onSelect = onSelect; this._inDialog = true; this.dpDiv.addClass(this._dialogClass); this._showDatepicker(this._dialogInput[0]); if ($.blockUI) { $.blockUI(this.dpDiv); } $.data(this._dialogInput[0], PROP_NAME, inst); return this; }, /* Detach a datepicker from its control. * @param target element - the target input field or division or span */ _destroyDatepicker: function(target) { var nodeName, $target = $(target), inst = $.data(target, PROP_NAME); if (!$target.hasClass(this.markerClassName)) { return; } nodeName = target.nodeName.toLowerCase(); $.removeData(target, PROP_NAME); if (nodeName === "input") { inst.append.remove(); inst.trigger.remove(); $target.removeClass(this.markerClassName). unbind("focus", this._showDatepicker). unbind("keydown", this._doKeyDown). unbind("keypress", this._doKeyPress). unbind("keyup", this._doKeyUp); } else if (nodeName === "div" || nodeName === "span") { $target.removeClass(this.markerClassName).empty(); } }, /* Enable the date picker to a jQuery selection. * @param target element - the target input field or division or span */ _enableDatepicker: function(target) { var nodeName, inline, $target = $(target), inst = $.data(target, PROP_NAME); if (!$target.hasClass(this.markerClassName)) { return; } nodeName = target.nodeName.toLowerCase(); if (nodeName === "input") { target.disabled = false; inst.trigger.filter("button"). each(function() { this.disabled = false; }).end(). filter("img").css({opacity: "1.0", cursor: ""}); } else if (nodeName === "div" || nodeName === "span") { inline = $target.children("." + this._inlineClass); inline.children().removeClass("ui-state-disabled"); inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). prop("disabled", false); } this._disabledInputs = $.map(this._disabledInputs, function(value) { return (value === target ? null : value); }); // delete entry }, /* Disable the date picker to a jQuery selection. * @param target element - the target input field or division or span */ _disableDatepicker: function(target) { var nodeName, inline, $target = $(target), inst = $.data(target, PROP_NAME); if (!$target.hasClass(this.markerClassName)) { return; } nodeName = target.nodeName.toLowerCase(); if (nodeName === "input") { target.disabled = true; inst.trigger.filter("button"). each(function() { this.disabled = true; }).end(). filter("img").css({opacity: "0.5", cursor: "default"}); } else if (nodeName === "div" || nodeName === "span") { inline = $target.children("." + this._inlineClass); inline.children().addClass("ui-state-disabled"); inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). prop("disabled", true); } this._disabledInputs = $.map(this._disabledInputs, function(value) { return (value === target ? null : value); }); // delete entry this._disabledInputs[this._disabledInputs.length] = target; }, /* Is the first field in a jQuery collection disabled as a datepicker? * @param target element - the target input field or division or span * @return boolean - true if disabled, false if enabled */ _isDisabledDatepicker: function(target) { if (!target) { return false; } for (var i = 0; i < this._disabledInputs.length; i++) { if (this._disabledInputs[i] === target) { return true; } } return false; }, /* Retrieve the instance data for the target control. * @param target element - the target input field or division or span * @return object - the associated instance data * @throws error if a jQuery problem getting data */ _getInst: function(target) { try { return $.data(target, PROP_NAME); } catch (err) { throw "Missing instance data for this datepicker"; } }, /* Update or retrieve the settings for a date picker attached to an input field or division. * @param target element - the target input field or division or span * @param name object - the new settings to update or * string - the name of the setting to change or retrieve, * when retrieving also "all" for all instance settings or * "defaults" for all global defaults * @param value any - the new value for the setting * (omit if above is an object or to retrieve a value) */ _optionDatepicker: function(target, name, value) { var settings, date, minDate, maxDate, inst = this._getInst(target); if (arguments.length === 2 && typeof name === "string") { return (name === "defaults" ? $.extend({}, $.datepicker._defaults) : (inst ? (name === "all" ? $.extend({}, inst.settings) : this._get(inst, name)) : null)); } settings = name || {}; if (typeof name === "string") { settings = {}; settings[name] = value; } if (inst) { if (this._curInst === inst) { this._hideDatepicker(); } date = this._getDateDatepicker(target, true); minDate = this._getMinMaxDate(inst, "min"); maxDate = this._getMinMaxDate(inst, "max"); extendRemove(inst.settings, settings); // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) { inst.settings.minDate = this._formatDate(inst, minDate); } if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) { inst.settings.maxDate = this._formatDate(inst, maxDate); } if ( "disabled" in settings ) { if ( settings.disabled ) { this._disableDatepicker(target); } else { this._enableDatepicker(target); } } this._attachments($(target), inst); this._autoSize(inst); this._setDate(inst, date); this._updateAlternate(inst); this._updateDatepicker(inst); } }, // change method deprecated _changeDatepicker: function(target, name, value) { this._optionDatepicker(target, name, value); }, /* Redraw the date picker attached to an input field or division. * @param target element - the target input field or division or span */ _refreshDatepicker: function(target) { var inst = this._getInst(target); if (inst) { this._updateDatepicker(inst); } }, /* Set the dates for a jQuery selection. * @param target element - the target input field or division or span * @param date Date - the new date */ _setDateDatepicker: function(target, date) { var inst = this._getInst(target); if (inst) { this._setDate(inst, date); this._updateDatepicker(inst); this._updateAlternate(inst); } }, /* Get the date(s) for the first entry in a jQuery selection. * @param target element - the target input field or division or span * @param noDefault boolean - true if no default date is to be used * @return Date - the current date */ _getDateDatepicker: function(target, noDefault) { var inst = this._getInst(target); if (inst && !inst.inline) { this._setDateFromField(inst, noDefault); } return (inst ? this._getDate(inst) : null); }, /* Handle keystrokes. */ _doKeyDown: function(event) { var onSelect, dateStr, sel, inst = $.datepicker._getInst(event.target), handled = true, isRTL = inst.dpDiv.is(".ui-datepicker-rtl"); inst._keyEvent = true; if ($.datepicker._datepickerShowing) { switch (event.keyCode) { case 9: $.datepicker._hideDatepicker(); handled = false; break; // hide on tab out case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." + $.datepicker._currentClass + ")", inst.dpDiv); if (sel[0]) { $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]); } onSelect = $.datepicker._get(inst, "onSelect"); if (onSelect) { dateStr = $.datepicker._formatDate(inst); // trigger custom callback onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); } else { $.datepicker._hideDatepicker(); } return false; // don't submit the form case 27: $.datepicker._hideDatepicker(); break; // hide on escape case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ? -$.datepicker._get(inst, "stepBigMonths") : -$.datepicker._get(inst, "stepMonths")), "M"); break; // previous month/year on page up/+ ctrl case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ? +$.datepicker._get(inst, "stepBigMonths") : +$.datepicker._get(inst, "stepMonths")), "M"); break; // next month/year on page down/+ ctrl case 35: if (event.ctrlKey || event.metaKey) { $.datepicker._clearDate(event.target); } handled = event.ctrlKey || event.metaKey; break; // clear on ctrl or command +end case 36: if (event.ctrlKey || event.metaKey) { $.datepicker._gotoToday(event.target); } handled = event.ctrlKey || event.metaKey; break; // current on ctrl or command +home case 37: if (event.ctrlKey || event.metaKey) { $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D"); } handled = event.ctrlKey || event.metaKey; // -1 day on ctrl or command +left if (event.originalEvent.altKey) { $.datepicker._adjustDate(event.target, (event.ctrlKey ? -$.datepicker._get(inst, "stepBigMonths") : -$.datepicker._get(inst, "stepMonths")), "M"); } // next month/year on alt +left on Mac break; case 38: if (event.ctrlKey || event.metaKey) { $.datepicker._adjustDate(event.target, -7, "D"); } handled = event.ctrlKey || event.metaKey; break; // -1 week on ctrl or command +up case 39: if (event.ctrlKey || event.metaKey) { $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D"); } handled = event.ctrlKey || event.metaKey; // +1 day on ctrl or command +right if (event.originalEvent.altKey) { $.datepicker._adjustDate(event.target, (event.ctrlKey ? +$.datepicker._get(inst, "stepBigMonths") : +$.datepicker._get(inst, "stepMonths")), "M"); } // next month/year on alt +right break; case 40: if (event.ctrlKey || event.metaKey) { $.datepicker._adjustDate(event.target, +7, "D"); } handled = event.ctrlKey || event.metaKey; break; // +1 week on ctrl or command +down default: handled = false; } } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home $.datepicker._showDatepicker(this); } else { handled = false; } if (handled) { event.preventDefault(); event.stopPropagation(); } }, /* Filter entered characters - based on date format. */ _doKeyPress: function(event) { var chars, chr, inst = $.datepicker._getInst(event.target); if ($.datepicker._get(inst, "constrainInput")) { chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat")); chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode); return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1); } }, /* Synchronise manual entry and field/alternate field. */ _doKeyUp: function(event) { var date, inst = $.datepicker._getInst(event.target); if (inst.input.val() !== inst.lastVal) { try { date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), (inst.input ? inst.input.val() : null), $.datepicker._getFormatConfig(inst)); if (date) { // only if valid $.datepicker._setDateFromField(inst); $.datepicker._updateAlternate(inst); $.datepicker._updateDatepicker(inst); } } catch (err) { } } return true; }, /* Pop-up the date picker for a given input field. * If false returned from beforeShow event handler do not show. * @param input element - the input field attached to the date picker or * event - if triggered by focus */ _showDatepicker: function(input) { input = input.target || input; if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger input = $("input", input.parentNode)[0]; } if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here return; } var inst, beforeShow, beforeShowSettings, isFixed, offset, showAnim, duration; inst = $.datepicker._getInst(input); if ($.datepicker._curInst && $.datepicker._curInst !== inst) { $.datepicker._curInst.dpDiv.stop(true, true); if ( inst && $.datepicker._datepickerShowing ) { $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] ); } } beforeShow = $.datepicker._get(inst, "beforeShow"); beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {}; if(beforeShowSettings === false){ return; } extendRemove(inst.settings, beforeShowSettings); inst.lastVal = null; $.datepicker._lastInput = input; $.datepicker._setDateFromField(inst); if ($.datepicker._inDialog) { // hide cursor input.value = ""; } if (!$.datepicker._pos) { // position below input $.datepicker._pos = $.datepicker._findPos(input); $.datepicker._pos[1] += input.offsetHeight; // add the height } isFixed = false; $(input).parents().each(function() { isFixed |= $(this).css("position") === "fixed"; return !isFixed; }); offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]}; $.datepicker._pos = null; //to avoid flashes on Firefox inst.dpDiv.empty(); // determine sizing offscreen inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"}); $.datepicker._updateDatepicker(inst); // fix width for dynamic number of date pickers // and adjust position before showing offset = $.datepicker._checkOffset(inst, offset, isFixed); inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ? "static" : (isFixed ? "fixed" : "absolute")), display: "none", left: offset.left + "px", top: offset.top + "px"}); if (!inst.inline) { showAnim = $.datepicker._get(inst, "showAnim"); duration = $.datepicker._get(inst, "duration"); inst.dpDiv.zIndex($(input).zIndex()+1); $.datepicker._datepickerShowing = true; if ( $.effects && $.effects.effect[ showAnim ] ) { inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration); } else { inst.dpDiv[showAnim || "show"](showAnim ? duration : null); } if ( $.datepicker._shouldFocusInput( inst ) ) { inst.input.focus(); } $.datepicker._curInst = inst; } }, /* Generate the date picker content. */ _updateDatepicker: function(inst) { this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) instActive = inst; // for delegate hover events inst.dpDiv.empty().append(this._generateHTML(inst)); this._attachHandlers(inst); inst.dpDiv.find("." + this._dayOverClass + " a").mouseover(); var origyearshtml, numMonths = this._getNumberOfMonths(inst), cols = numMonths[1], width = 17; inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""); if (cols > 1) { inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em"); } inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") + "Class"]("ui-datepicker-multi"); inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") + "Class"]("ui-datepicker-rtl"); if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { inst.input.focus(); } // deffered render of the years select (to avoid flashes on Firefox) if( inst.yearshtml ){ origyearshtml = inst.yearshtml; setTimeout(function(){ //assure that inst.yearshtml didn't change. if( origyearshtml === inst.yearshtml && inst.yearshtml ){ inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml); } origyearshtml = inst.yearshtml = null; }, 0); } }, // #6694 - don't focus the input if it's already focused // this breaks the change event in IE // Support: IE and jQuery <1.9 _shouldFocusInput: function( inst ) { return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); }, /* Check positioning to remain on screen. */ _checkOffset: function(inst, offset, isFixed) { var dpWidth = inst.dpDiv.outerWidth(), dpHeight = inst.dpDiv.outerHeight(), inputWidth = inst.input ? inst.input.outerWidth() : 0, inputHeight = inst.input ? inst.input.outerHeight() : 0, viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()), viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop()); offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0); offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0; offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0; // now check if datepicker is showing outside window viewport - move to a better place if so. offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? Math.abs(offset.left + dpWidth - viewWidth) : 0); offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? Math.abs(dpHeight + inputHeight) : 0); return offset; }, /* Find an object's position on the screen. */ _findPos: function(obj) { var position, inst = this._getInst(obj), isRTL = this._get(inst, "isRTL"); while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) { obj = obj[isRTL ? "previousSibling" : "nextSibling"]; } position = $(obj).offset(); return [position.left, position.top]; }, /* Hide the date picker from view. * @param input element - the input field attached to the date picker */ _hideDatepicker: function(input) { var showAnim, duration, postProcess, onClose, inst = this._curInst; if (!inst || (input && inst !== $.data(input, PROP_NAME))) { return; } if (this._datepickerShowing) { showAnim = this._get(inst, "showAnim"); duration = this._get(inst, "duration"); postProcess = function() { $.datepicker._tidyDialog(inst); }; // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess); } else { inst.dpDiv[(showAnim === "slideDown" ? "slideUp" : (showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess); } if (!showAnim) { postProcess(); } this._datepickerShowing = false; onClose = this._get(inst, "onClose"); if (onClose) { onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]); } this._lastInput = null; if (this._inDialog) { this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" }); if ($.blockUI) { $.unblockUI(); $("body").append(this.dpDiv); } } this._inDialog = false; } }, /* Tidy up after a dialog display. */ _tidyDialog: function(inst) { inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar"); }, /* Close date picker if clicked elsewhere. */ _checkExternalClick: function(event) { if (!$.datepicker._curInst) { return; } var $target = $(event.target), inst = $.datepicker._getInst($target[0]); if ( ( ( $target[0].id !== $.datepicker._mainDivId && $target.parents("#" + $.datepicker._mainDivId).length === 0 && !$target.hasClass($.datepicker.markerClassName) && !$target.closest("." + $.datepicker._triggerClass).length && $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) { $.datepicker._hideDatepicker(); } }, /* Adjust one of the date sub-fields. */ _adjustDate: function(id, offset, period) { var target = $(id), inst = this._getInst(target[0]); if (this._isDisabledDatepicker(target[0])) { return; } this._adjustInstDate(inst, offset + (period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning period); this._updateDatepicker(inst); }, /* Action for current link. */ _gotoToday: function(id) { var date, target = $(id), inst = this._getInst(target[0]); if (this._get(inst, "gotoCurrent") && inst.currentDay) { inst.selectedDay = inst.currentDay; inst.drawMonth = inst.selectedMonth = inst.currentMonth; inst.drawYear = inst.selectedYear = inst.currentYear; } else { date = new Date(); inst.selectedDay = date.getDate(); inst.drawMonth = inst.selectedMonth = date.getMonth(); inst.drawYear = inst.selectedYear = date.getFullYear(); } this._notifyChange(inst); this._adjustDate(target); }, /* Action for selecting a new month/year. */ _selectMonthYear: function(id, select, period) { var target = $(id), inst = this._getInst(target[0]); inst["selected" + (period === "M" ? "Month" : "Year")] = inst["draw" + (period === "M" ? "Month" : "Year")] = parseInt(select.options[select.selectedIndex].value,10); this._notifyChange(inst); this._adjustDate(target); }, /* Action for selecting a day. */ _selectDay: function(id, month, year, td) { var inst, target = $(id); if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) { return; } inst = this._getInst(target[0]); inst.selectedDay = inst.currentDay = $("a", td).html(); inst.selectedMonth = inst.currentMonth = month; inst.selectedYear = inst.currentYear = year; this._selectDate(id, this._formatDate(inst, inst.currentDay, inst.currentMonth, inst.currentYear)); }, /* Erase the input field and hide the date picker. */ _clearDate: function(id) { var target = $(id); this._selectDate(target, ""); }, /* Update the input field with the selected date. */ _selectDate: function(id, dateStr) { var onSelect, target = $(id), inst = this._getInst(target[0]); dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); if (inst.input) { inst.input.val(dateStr); } this._updateAlternate(inst); onSelect = this._get(inst, "onSelect"); if (onSelect) { onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback } else if (inst.input) { inst.input.trigger("change"); // fire the change event } if (inst.inline){ this._updateDatepicker(inst); } else { this._hideDatepicker(); this._lastInput = inst.input[0]; if (typeof(inst.input[0]) !== "object") { inst.input.focus(); // restore focus } this._lastInput = null; } }, /* Update any alternate field to synchronise with the main field. */ _updateAlternate: function(inst) { var altFormat, date, dateStr, altField = this._get(inst, "altField"); if (altField) { // update alternate field too altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat"); date = this._getDate(inst); dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst)); $(altField).each(function() { $(this).val(dateStr); }); } }, /* Set as beforeShowDay function to prevent selection of weekends. * @param date Date - the date to customise * @return [boolean, string] - is this date selectable?, what is its CSS class? */ noWeekends: function(date) { var day = date.getDay(); return [(day > 0 && day < 6), ""]; }, /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. * @param date Date - the date to get the week for * @return number - the number of the week within the year that contains this date */ iso8601Week: function(date) { var time, checkDate = new Date(date.getTime()); // Find Thursday of this week starting on Monday checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); time = checkDate.getTime(); checkDate.setMonth(0); // Compare with Jan 1 checkDate.setDate(1); return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; }, /* Parse a string value into a date object. * See formatDate below for the possible formats. * * @param format string - the expected format of the date * @param value string - the date in the above format * @param settings Object - attributes include: * shortYearCutoff number - the cutoff year for determining the century (optional) * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) * dayNames string[7] - names of the days from Sunday (optional) * monthNamesShort string[12] - abbreviated names of the months (optional) * monthNames string[12] - names of the months (optional) * @return Date - the extracted date value or null if value is blank */ parseDate: function (format, value, settings) { if (format == null || value == null) { throw "Invalid arguments"; } value = (typeof value === "object" ? value.toString() : value + ""); if (value === "") { return null; } var iFormat, dim, extra, iValue = 0, shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff, shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)), dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, year = -1, month = -1, day = -1, doy = -1, literal = false, date, // Check whether a format character is doubled lookAhead = function(match) { var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); if (matches) { iFormat++; } return matches; }, // Extract a number from the string value getNumber = function(match) { var isDoubled = lookAhead(match), size = (match === "@" ? 14 : (match === "!" ? 20 : (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))), digits = new RegExp("^\\d{1," + size + "}"), num = value.substring(iValue).match(digits); if (!num) { throw "Missing number at position " + iValue; } iValue += num[0].length; return parseInt(num[0], 10); }, // Extract a name from the string value and convert to an index getName = function(match, shortNames, longNames) { var index = -1, names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) { return [ [k, v] ]; }).sort(function (a, b) { return -(a[1].length - b[1].length); }); $.each(names, function (i, pair) { var name = pair[1]; if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) { index = pair[0]; iValue += name.length; return false; } }); if (index !== -1) { return index + 1; } else { throw "Unknown name at position " + iValue; } }, // Confirm that a literal character matches the string value checkLiteral = function() { if (value.charAt(iValue) !== format.charAt(iFormat)) { throw "Unexpected literal at position " + iValue; } iValue++; }; for (iFormat = 0; iFormat < format.length; iFormat++) { if (literal) { if (format.charAt(iFormat) === "'" && !lookAhead("'")) { literal = false; } else { checkLiteral(); } } else { switch (format.charAt(iFormat)) { case "d": day = getNumber("d"); break; case "D": getName("D", dayNamesShort, dayNames); break; case "o": doy = getNumber("o"); break; case "m": month = getNumber("m"); break; case "M": month = getName("M", monthNamesShort, monthNames); break; case "y": year = getNumber("y"); break; case "@": date = new Date(getNumber("@")); year = date.getFullYear(); month = date.getMonth() + 1; day = date.getDate(); break; case "!": date = new Date((getNumber("!") - this._ticksTo1970) / 10000); year = date.getFullYear(); month = date.getMonth() + 1; day = date.getDate(); break; case "'": if (lookAhead("'")){ checkLiteral(); } else { literal = true; } break; default: checkLiteral(); } } } if (iValue < value.length){ extra = value.substr(iValue); if (!/^\s+/.test(extra)) { throw "Extra/unparsed characters found in date: " + extra; } } if (year === -1) { year = new Date().getFullYear(); } else if (year < 100) { year += new Date().getFullYear() - new Date().getFullYear() % 100 + (year <= shortYearCutoff ? 0 : -100); } if (doy > -1) { month = 1; day = doy; do { dim = this._getDaysInMonth(year, month - 1); if (day <= dim) { break; } month++; day -= dim; } while (true); } date = this._daylightSavingAdjust(new Date(year, month - 1, day)); if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) { throw "Invalid date"; // E.g. 31/02/00 } return date; }, /* Standard date formats. */ ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) COOKIE: "D, dd M yy", ISO_8601: "yy-mm-dd", RFC_822: "D, d M y", RFC_850: "DD, dd-M-y", RFC_1036: "D, d M y", RFC_1123: "D, d M yy", RFC_2822: "D, d M yy", RSS: "D, d M y", // RFC 822 TICKS: "!", TIMESTAMP: "@", W3C: "yy-mm-dd", // ISO 8601 _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), /* Format a date object into a string value. * The format can be combinations of the following: * d - day of month (no leading zero) * dd - day of month (two digit) * o - day of year (no leading zeros) * oo - day of year (three digit) * D - day name short * DD - day name long * m - month of year (no leading zero) * mm - month of year (two digit) * M - month name short * MM - month name long * y - year (two digit) * yy - year (four digit) * @ - Unix timestamp (ms since 01/01/1970) * ! - Windows ticks (100ns since 01/01/0001) * "..." - literal text * '' - single quote * * @param format string - the desired format of the date * @param date Date - the date value to format * @param settings Object - attributes include: * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) * dayNames string[7] - names of the days from Sunday (optional) * monthNamesShort string[12] - abbreviated names of the months (optional) * monthNames string[12] - names of the months (optional) * @return string - the date in the above format */ formatDate: function (format, date, settings) { if (!date) { return ""; } var iFormat, dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, // Check whether a format character is doubled lookAhead = function(match) { var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); if (matches) { iFormat++; } return matches; }, // Format a number, with leading zero if necessary formatNumber = function(match, value, len) { var num = "" + value; if (lookAhead(match)) { while (num.length < len) { num = "0" + num; } } return num; }, // Format a name, short or long as requested formatName = function(match, value, shortNames, longNames) { return (lookAhead(match) ? longNames[value] : shortNames[value]); }, output = "", literal = false; if (date) { for (iFormat = 0; iFormat < format.length; iFormat++) { if (literal) { if (format.charAt(iFormat) === "'" && !lookAhead("'")) { literal = false; } else { output += format.charAt(iFormat); } } else { switch (format.charAt(iFormat)) { case "d": output += formatNumber("d", date.getDate(), 2); break; case "D": output += formatName("D", date.getDay(), dayNamesShort, dayNames); break; case "o": output += formatNumber("o", Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3); break; case "m": output += formatNumber("m", date.getMonth() + 1, 2); break; case "M": output += formatName("M", date.getMonth(), monthNamesShort, monthNames); break; case "y": output += (lookAhead("y") ? date.getFullYear() : (date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100); break; case "@": output += date.getTime(); break; case "!": output += date.getTime() * 10000 + this._ticksTo1970; break; case "'": if (lookAhead("'")) { output += "'"; } else { literal = true; } break; default: output += format.charAt(iFormat); } } } } return output; }, /* Extract all possible characters from the date format. */ _possibleChars: function (format) { var iFormat, chars = "", literal = false, // Check whether a format character is doubled lookAhead = function(match) { var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); if (matches) { iFormat++; } return matches; }; for (iFormat = 0; iFormat < format.length; iFormat++) { if (literal) { if (format.charAt(iFormat) === "'" && !lookAhead("'")) { literal = false; } else { chars += format.charAt(iFormat); } } else { switch (format.charAt(iFormat)) { case "d": case "m": case "y": case "@": chars += "0123456789"; break; case "D": case "M": return null; // Accept anything case "'": if (lookAhead("'")) { chars += "'"; } else { literal = true; } break; default: chars += format.charAt(iFormat); } } } return chars; }, /* Get a setting value, defaulting if necessary. */ _get: function(inst, name) { return inst.settings[name] !== undefined ? inst.settings[name] : this._defaults[name]; }, /* Parse existing date and initialise date picker. */ _setDateFromField: function(inst, noDefault) { if (inst.input.val() === inst.lastVal) { return; } var dateFormat = this._get(inst, "dateFormat"), dates = inst.lastVal = inst.input ? inst.input.val() : null, defaultDate = this._getDefaultDate(inst), date = defaultDate, settings = this._getFormatConfig(inst); try { date = this.parseDate(dateFormat, dates, settings) || defaultDate; } catch (event) { dates = (noDefault ? "" : dates); } inst.selectedDay = date.getDate(); inst.drawMonth = inst.selectedMonth = date.getMonth(); inst.drawYear = inst.selectedYear = date.getFullYear(); inst.currentDay = (dates ? date.getDate() : 0); inst.currentMonth = (dates ? date.getMonth() : 0); inst.currentYear = (dates ? date.getFullYear() : 0); this._adjustInstDate(inst); }, /* Retrieve the default date shown on opening. */ _getDefaultDate: function(inst) { return this._restrictMinMax(inst, this._determineDate(inst, this._get(inst, "defaultDate"), new Date())); }, /* A date may be specified as an exact value or a relative one. */ _determineDate: function(inst, date, defaultDate) { var offsetNumeric = function(offset) { var date = new Date(); date.setDate(date.getDate() + offset); return date; }, offsetString = function(offset) { try { return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), offset, $.datepicker._getFormatConfig(inst)); } catch (e) { // Ignore } var date = (offset.toLowerCase().match(/^c/) ? $.datepicker._getDate(inst) : null) || new Date(), year = date.getFullYear(), month = date.getMonth(), day = date.getDate(), pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, matches = pattern.exec(offset); while (matches) { switch (matches[2] || "d") { case "d" : case "D" : day += parseInt(matches[1],10); break; case "w" : case "W" : day += parseInt(matches[1],10) * 7; break; case "m" : case "M" : month += parseInt(matches[1],10); day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); break; case "y": case "Y" : year += parseInt(matches[1],10); day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); break; } matches = pattern.exec(offset); } return new Date(year, month, day); }, newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) : (typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime())))); newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate); if (newDate) { newDate.setHours(0); newDate.setMinutes(0); newDate.setSeconds(0); newDate.setMilliseconds(0); } return this._daylightSavingAdjust(newDate); }, /* Handle switch to/from daylight saving. * Hours may be non-zero on daylight saving cut-over: * > 12 when midnight changeover, but then cannot generate * midnight datetime, so jump to 1AM, otherwise reset. * @param date (Date) the date to check * @return (Date) the corrected date */ _daylightSavingAdjust: function(date) { if (!date) { return null; } date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0); return date; }, /* Set the date(s) directly. */ _setDate: function(inst, date, noChange) { var clear = !date, origMonth = inst.selectedMonth, origYear = inst.selectedYear, newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date())); inst.selectedDay = inst.currentDay = newDate.getDate(); inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) { this._notifyChange(inst); } this._adjustInstDate(inst); if (inst.input) { inst.input.val(clear ? "" : this._formatDate(inst)); } }, /* Retrieve the date(s) directly. */ _getDate: function(inst) { var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null : this._daylightSavingAdjust(new Date( inst.currentYear, inst.currentMonth, inst.currentDay))); return startDate; }, /* Attach the onxxx handlers. These are declared statically so * they work with static code transformers like Caja. */ _attachHandlers: function(inst) { var stepMonths = this._get(inst, "stepMonths"), id = "#" + inst.id.replace( /\\\\/g, "\\" ); inst.dpDiv.find("[data-handler]").map(function () { var handler = { prev: function () { $.datepicker._adjustDate(id, -stepMonths, "M"); }, next: function () { $.datepicker._adjustDate(id, +stepMonths, "M"); }, hide: function () { $.datepicker._hideDatepicker(); }, today: function () { $.datepicker._gotoToday(id); }, selectDay: function () { $.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this); return false; }, selectMonth: function () { $.datepicker._selectMonthYear(id, this, "M"); return false; }, selectYear: function () { $.datepicker._selectMonthYear(id, this, "Y"); return false; } }; $(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]); }); }, /* Generate the HTML for the current state of the date picker. */ _generateHTML: function(inst) { var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, monthNames, monthNamesShort, beforeShowDay, showOtherMonths, selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, printDate, dRow, tbody, daySettings, otherMonth, unselectable, tempDate = new Date(), today = this._daylightSavingAdjust( new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time isRTL = this._get(inst, "isRTL"), showButtonPanel = this._get(inst, "showButtonPanel"), hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"), navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"), numMonths = this._getNumberOfMonths(inst), showCurrentAtPos = this._get(inst, "showCurrentAtPos"), stepMonths = this._get(inst, "stepMonths"), isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1), currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) : new Date(inst.currentYear, inst.currentMonth, inst.currentDay))), minDate = this._getMinMaxDate(inst, "min"), maxDate = this._getMinMaxDate(inst, "max"), drawMonth = inst.drawMonth - showCurrentAtPos, drawYear = inst.drawYear; if (drawMonth < 0) { drawMonth += 12; drawYear--; } if (maxDate) { maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate())); maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw); while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { drawMonth--; if (drawMonth < 0) { drawMonth = 11; drawYear--; } } } inst.drawMonth = drawMonth; inst.drawYear = drawYear; prevText = this._get(inst, "prevText"); prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText, this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), this._getFormatConfig(inst))); prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? "" + prevText + "" : (hideIfNoPrevNext ? "" : "" + prevText + "")); nextText = this._get(inst, "nextText"); nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText, this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), this._getFormatConfig(inst))); next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? "" + nextText + "" : (hideIfNoPrevNext ? "" : "" + nextText + "")); currentText = this._get(inst, "currentText"); gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today); currentText = (!navigationAsDateFormat ? currentText : this.formatDate(currentText, gotoDate, this._getFormatConfig(inst))); controls = (!inst.inline ? "" : ""); buttonPanel = (showButtonPanel) ? "
    " + (isRTL ? controls : "") + (this._isInRange(inst, gotoDate) ? "" : "") + (isRTL ? "" : controls) + "
    " : ""; firstDay = parseInt(this._get(inst, "firstDay"),10); firstDay = (isNaN(firstDay) ? 0 : firstDay); showWeek = this._get(inst, "showWeek"); dayNames = this._get(inst, "dayNames"); dayNamesMin = this._get(inst, "dayNamesMin"); monthNames = this._get(inst, "monthNames"); monthNamesShort = this._get(inst, "monthNamesShort"); beforeShowDay = this._get(inst, "beforeShowDay"); showOtherMonths = this._get(inst, "showOtherMonths"); selectOtherMonths = this._get(inst, "selectOtherMonths"); defaultDate = this._getDefaultDate(inst); html = ""; dow; for (row = 0; row < numMonths[0]; row++) { group = ""; this.maxRows = 4; for (col = 0; col < numMonths[1]; col++) { selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); cornerClass = " ui-corner-all"; calender = ""; if (isMultiMonth) { calender += "
    "; } calender += "
    " + (/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") + (/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") + this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers "
    " + ""; thead = (showWeek ? "" : ""); for (dow = 0; dow < 7; dow++) { // days of the week day = (dow + firstDay) % 7; thead += "= 5 ? " class='ui-datepicker-week-end'" : "") + ">" + "" + dayNamesMin[day] + ""; } calender += thead + ""; daysInMonth = this._getDaysInMonth(drawYear, drawMonth); if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) { inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); } leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043) this.maxRows = numRows; printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows calender += ""; tbody = (!showWeek ? "" : ""); for (dow = 0; dow < 7; dow++) { // create date picker days daySettings = (beforeShowDay ? beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]); otherMonth = (printDate.getMonth() !== drawMonth); unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] || (minDate && printDate < minDate) || (maxDate && printDate > maxDate); tbody += ""; // display selectable date printDate.setDate(printDate.getDate() + 1); printDate = this._daylightSavingAdjust(printDate); } calender += tbody + ""; } drawMonth++; if (drawMonth > 11) { drawMonth = 0; drawYear++; } calender += "
    " + this._get(inst, "weekHeader") + "
    " + this._get(inst, "calculateWeek")(printDate) + "" + // actions (otherMonth && !showOtherMonths ? " " : // display for other months (unselectable ? "" + printDate.getDate() + "" : "" + printDate.getDate() + "")) + "
    " + (isMultiMonth ? "
    " + ((numMonths[0] > 0 && col === numMonths[1]-1) ? "
    " : "") : ""); group += calender; } html += group; } html += buttonPanel; inst._keyEvent = false; return html; }, /* Generate the month and year header. */ _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, secondary, monthNames, monthNamesShort) { var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, changeMonth = this._get(inst, "changeMonth"), changeYear = this._get(inst, "changeYear"), showMonthAfterYear = this._get(inst, "showMonthAfterYear"), html = "
    ", monthHtml = ""; // month selection if (secondary || !changeMonth) { monthHtml += "" + monthNames[drawMonth] + ""; } else { inMinYear = (minDate && minDate.getFullYear() === drawYear); inMaxYear = (maxDate && maxDate.getFullYear() === drawYear); monthHtml += ""; } if (!showMonthAfterYear) { html += monthHtml + (secondary || !(changeMonth && changeYear) ? " " : ""); } // year selection if ( !inst.yearshtml ) { inst.yearshtml = ""; if (secondary || !changeYear) { html += "" + drawYear + ""; } else { // determine range of years to display years = this._get(inst, "yearRange").split(":"); thisYear = new Date().getFullYear(); determineYear = function(value) { var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) : (value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) : parseInt(value, 10))); return (isNaN(year) ? thisYear : year); }; year = determineYear(years[0]); endYear = Math.max(year, determineYear(years[1] || "")); year = (minDate ? Math.max(year, minDate.getFullYear()) : year); endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); inst.yearshtml += ""; html += inst.yearshtml; inst.yearshtml = null; } } html += this._get(inst, "yearSuffix"); if (showMonthAfterYear) { html += (secondary || !(changeMonth && changeYear) ? " " : "") + monthHtml; } html += "
    "; // Close datepicker_header return html; }, /* Adjust one of the date sub-fields. */ _adjustInstDate: function(inst, offset, period) { var year = inst.drawYear + (period === "Y" ? offset : 0), month = inst.drawMonth + (period === "M" ? offset : 0), day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0), date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day))); inst.selectedDay = date.getDate(); inst.drawMonth = inst.selectedMonth = date.getMonth(); inst.drawYear = inst.selectedYear = date.getFullYear(); if (period === "M" || period === "Y") { this._notifyChange(inst); } }, /* Ensure a date is within any min/max bounds. */ _restrictMinMax: function(inst, date) { var minDate = this._getMinMaxDate(inst, "min"), maxDate = this._getMinMaxDate(inst, "max"), newDate = (minDate && date < minDate ? minDate : date); return (maxDate && newDate > maxDate ? maxDate : newDate); }, /* Notify change of month/year. */ _notifyChange: function(inst) { var onChange = this._get(inst, "onChangeMonthYear"); if (onChange) { onChange.apply((inst.input ? inst.input[0] : null), [inst.selectedYear, inst.selectedMonth + 1, inst]); } }, /* Determine the number of months to show. */ _getNumberOfMonths: function(inst) { var numMonths = this._get(inst, "numberOfMonths"); return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths)); }, /* Determine the current maximum date - ensure no time components are set. */ _getMinMaxDate: function(inst, minMax) { return this._determineDate(inst, this._get(inst, minMax + "Date"), null); }, /* Find the number of days in a given month. */ _getDaysInMonth: function(year, month) { return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate(); }, /* Find the day of the week of the first of a month. */ _getFirstDayOfMonth: function(year, month) { return new Date(year, month, 1).getDay(); }, /* Determines if we should allow a "next/prev" month display change. */ _canAdjustMonth: function(inst, offset, curYear, curMonth) { var numMonths = this._getNumberOfMonths(inst), date = this._daylightSavingAdjust(new Date(curYear, curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1)); if (offset < 0) { date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); } return this._isInRange(inst, date); }, /* Is the given date in the accepted range? */ _isInRange: function(inst, date) { var yearSplit, currentYear, minDate = this._getMinMaxDate(inst, "min"), maxDate = this._getMinMaxDate(inst, "max"), minYear = null, maxYear = null, years = this._get(inst, "yearRange"); if (years){ yearSplit = years.split(":"); currentYear = new Date().getFullYear(); minYear = parseInt(yearSplit[0], 10); maxYear = parseInt(yearSplit[1], 10); if ( yearSplit[0].match(/[+\-].*/) ) { minYear += currentYear; } if ( yearSplit[1].match(/[+\-].*/) ) { maxYear += currentYear; } } return ((!minDate || date.getTime() >= minDate.getTime()) && (!maxDate || date.getTime() <= maxDate.getTime()) && (!minYear || date.getFullYear() >= minYear) && (!maxYear || date.getFullYear() <= maxYear)); }, /* Provide the configuration settings for formatting/parsing. */ _getFormatConfig: function(inst) { var shortYearCutoff = this._get(inst, "shortYearCutoff"); shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff : new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); return {shortYearCutoff: shortYearCutoff, dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"), monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")}; }, /* Format the given date for display. */ _formatDate: function(inst, day, month, year) { if (!day) { inst.currentDay = inst.selectedDay; inst.currentMonth = inst.selectedMonth; inst.currentYear = inst.selectedYear; } var date = (day ? (typeof day === "object" ? day : this._daylightSavingAdjust(new Date(year, month, day))) : this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst)); } }); /* * Bind hover events for datepicker elements. * Done via delegate so the binding only occurs once in the lifetime of the parent div. * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. */ function bindHover(dpDiv) { var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; return dpDiv.delegate(selector, "mouseout", function() { $(this).removeClass("ui-state-hover"); if (this.className.indexOf("ui-datepicker-prev") !== -1) { $(this).removeClass("ui-datepicker-prev-hover"); } if (this.className.indexOf("ui-datepicker-next") !== -1) { $(this).removeClass("ui-datepicker-next-hover"); } }) .delegate(selector, "mouseover", function(){ if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) { $(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"); $(this).addClass("ui-state-hover"); if (this.className.indexOf("ui-datepicker-prev") !== -1) { $(this).addClass("ui-datepicker-prev-hover"); } if (this.className.indexOf("ui-datepicker-next") !== -1) { $(this).addClass("ui-datepicker-next-hover"); } } }); } /* jQuery extend now ignores nulls! */ function extendRemove(target, props) { $.extend(target, props); for (var name in props) { if (props[name] == null) { target[name] = props[name]; } } return target; } /* Invoke the datepicker functionality. @param options string - a command, optionally followed by additional parameters or Object - settings for attaching new datepicker functionality @return jQuery object */ $.fn.datepicker = function(options){ /* Verify an empty collection wasn't passed - Fixes #6976 */ if ( !this.length ) { return this; } /* Initialise the date picker. */ if (!$.datepicker.initialized) { $(document).mousedown($.datepicker._checkExternalClick); $.datepicker.initialized = true; } /* Append datepicker main container to body if not exist. */ if ($("#"+$.datepicker._mainDivId).length === 0) { $("body").append($.datepicker.dpDiv); } var otherArgs = Array.prototype.slice.call(arguments, 1); if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) { return $.datepicker["_" + options + "Datepicker"]. apply($.datepicker, [this[0]].concat(otherArgs)); } if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") { return $.datepicker["_" + options + "Datepicker"]. apply($.datepicker, [this[0]].concat(otherArgs)); } return this.each(function() { typeof options === "string" ? $.datepicker["_" + options + "Datepicker"]. apply($.datepicker, [this].concat(otherArgs)) : $.datepicker._attachDatepicker(this, options); }); }; $.datepicker = new Datepicker(); // singleton instance $.datepicker.initialized = false; $.datepicker.uuid = new Date().getTime(); $.datepicker.version = "1.10.4"; })(jQuery); (function( $, undefined ) { var sizeRelatedOptions = { buttons: true, height: true, maxHeight: true, maxWidth: true, minHeight: true, minWidth: true, width: true }, resizableRelatedOptions = { maxHeight: true, maxWidth: true, minHeight: true, minWidth: true }; $.widget( "ui.dialog", { version: "1.10.4", options: { appendTo: "body", autoOpen: true, buttons: [], closeOnEscape: true, closeText: "close", dialogClass: "", draggable: true, hide: null, height: "auto", maxHeight: null, maxWidth: null, minHeight: 150, minWidth: 150, modal: false, position: { my: "center", at: "center", of: window, collision: "fit", // Ensure the titlebar is always visible using: function( pos ) { var topOffset = $( this ).css( pos ).offset().top; if ( topOffset < 0 ) { $( this ).css( "top", pos.top - topOffset ); } } }, resizable: true, show: null, title: null, width: 300, // callbacks beforeClose: null, close: null, drag: null, dragStart: null, dragStop: null, focus: null, open: null, resize: null, resizeStart: null, resizeStop: null }, _create: function() { this.originalCss = { display: this.element[0].style.display, width: this.element[0].style.width, minHeight: this.element[0].style.minHeight, maxHeight: this.element[0].style.maxHeight, height: this.element[0].style.height }; this.originalPosition = { parent: this.element.parent(), index: this.element.parent().children().index( this.element ) }; this.originalTitle = this.element.attr("title"); this.options.title = this.options.title || this.originalTitle; this._createWrapper(); this.element .show() .removeAttr("title") .addClass("ui-dialog-content ui-widget-content") .appendTo( this.uiDialog ); this._createTitlebar(); this._createButtonPane(); if ( this.options.draggable && $.fn.draggable ) { this._makeDraggable(); } if ( this.options.resizable && $.fn.resizable ) { this._makeResizable(); } this._isOpen = false; }, _init: function() { if ( this.options.autoOpen ) { this.open(); } }, _appendTo: function() { var element = this.options.appendTo; if ( element && (element.jquery || element.nodeType) ) { return $( element ); } return this.document.find( element || "body" ).eq( 0 ); }, _destroy: function() { var next, originalPosition = this.originalPosition; this._destroyOverlay(); this.element .removeUniqueId() .removeClass("ui-dialog-content ui-widget-content") .css( this.originalCss ) // Without detaching first, the following becomes really slow .detach(); this.uiDialog.stop( true, true ).remove(); if ( this.originalTitle ) { this.element.attr( "title", this.originalTitle ); } next = originalPosition.parent.children().eq( originalPosition.index ); // Don't try to place the dialog next to itself (#8613) if ( next.length && next[0] !== this.element[0] ) { next.before( this.element ); } else { originalPosition.parent.append( this.element ); } }, widget: function() { return this.uiDialog; }, disable: $.noop, enable: $.noop, close: function( event ) { var activeElement, that = this; if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) { return; } this._isOpen = false; this._destroyOverlay(); if ( !this.opener.filter(":focusable").focus().length ) { // support: IE9 // IE9 throws an "Unspecified error" accessing document.activeElement from an