Showing preview only (1,040K chars total). Download the full file or copy to clipboard to get everything.
Repository: honsiorovskyi/open-url-in-container
Branch: master
Commit: 4fa376570305
Files: 38
Total size: 1008.9 KB
Directory structure:
gitextract_d1ti6rlb/
├── .gitignore
├── CONTRIBUTORS
├── LICENSE
├── README.md
├── bin/
│ └── launcher.sh
├── build/
│ ├── .eslintrc.json
│ └── package.json
└── src/
├── js/
│ ├── config.js
│ ├── containers.js
│ ├── opener/
│ │ ├── opener.js
│ │ ├── parser.js
│ │ └── validator.js
│ ├── params.js
│ ├── popup/
│ │ ├── bookmark.js
│ │ ├── containers.js
│ │ ├── dom.js
│ │ ├── folders.js
│ │ ├── popup.js
│ │ ├── signature.js
│ │ ├── state.js
│ │ ├── terminal.js
│ │ └── url.js
│ ├── security/
│ │ ├── hex.js
│ │ ├── keys.js
│ │ └── signature.js
│ └── tabs.js
├── manifest.json
├── opener.html
├── popup.html
└── tests/
├── lib/
│ ├── chai/
│ │ └── chai.js
│ └── mocha/
│ ├── mocha.css
│ └── mocha.js
├── opener/
│ ├── parsers.test.js
│ └── validator.test.js
├── security/
│ └── hex.test.js
├── testdata/
│ └── example-links.html
├── unit.html
└── unit.test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/build/node_modules
/build/web-ext-artifacts
================================================
FILE: CONTRIBUTORS
================================================
Extension code by:
- Denys Honsiorovskyi (https://github.com/honsiorovskyi)
- attero (https://github.com/apfelchips)
Extension icons by:
- Pixel perfect (https://www.flaticon.com/authors/pixel-perfect from https://www.flaticon.com/)
- Freepik (https://www.flaticon.com/authors/freepik) from https://www.flaticon.com/
- Google (https://www.flaticon.com/authors/google) from https://flaticon.com/
- Denys Honsiorovskyi (https://github.com/honsiorovskyi)
================================================
FILE: LICENSE
================================================
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
================================================
FILE: README.md
================================================
# Open external links in a container
<img src="src/icons/extension-96.png">
_**Important:** this code corresponds to an early preview of the future 2.x.x branch. If you want to access the current stable code, please see [version 1.0.3](https://github.com/honsiorovskyi/open-url-in-container/tree/1.0.3)._
This is a Firefox extension that enables support for opening links in specific containers using custom protocol handler.
It works for terminal, OS shortcuts, bookmarks, password managers, regular HTML pages and many other things.
Also it features a small popup that can be called from the icon in the address bar
which provides a way to get the links or terminal commands for opening current page
in any available container.
This extension can be installed from the [official Mozilla Add-Ons Store for Firefox](https://addons.mozilla.org/firefox/addon/open-url-in-container/).

## Features
- provides custom protocol handler to open URLs in containers
- provides a UI to generate links, bookmarks and terminal commands
- supports both command line and internal invocations
- supports creation of containers on the fly
- supports setting colors and icons when creating new containers
- supports tabs pinning
- supports opening tabs in reader mode
- works well in combination with other extensions
## Examples
Open `https://mozilla.org` in a container named `MyContainer`.
```bash
$ firefox 'ext+container:name=MyContainer&url=https://mozilla.org&signature=ea7214f675398e93764ba44504070221633b0d5dce6c4263715f1cca89ab5f86'
```
Open `https://mozilla.org` in a container named `MyContainer`. If the container doesn't exist, create it using an `orange` colored `fruit` icon. Also, pin the tab.
```bash
$ firefox 'ext+container:name=MyContainer&color=orange&icon=fruit&url=https://mozilla.org&pinned=true&signature=ea7214f675398e93764ba44504070221633b0d5dce6c4263715f1cca89ab5f86'
```
Also it will work with the [links on the site](ext+container:name=MyContainer&url=https://mozilla.org):
```html
<a href="ext+container:name=MyContainer&url=https://mozilla.org&signature=ea7214f675398e93764ba44504070221633b0d5dce6c4263715f1cca89ab5f86">Mozilla.Org in MyContainer</a>
```
### What is signature?
Signature is very simple cryptographic signature of the URL passed to the extension.
It is needed to protect you from situations when someone with malicious intentions,
knowing that for instance you have a container named `Personal` with your private information,
could somehow force you to click on a link that looks like `ext+container:name=Personal&url=https://evil.com/correlationID=XXX`,
and therefore, thanks to `XXX` passed to `evil.com` in the `Personal` container, and the same `XXX` passed to the same `evil.com`
but openly in a public container, track your identity across the containers.
To prevent it from happening, this extension will do the following:
1. If it receives a link without signature (e.g. `ext+container:name=Personal&url=https://evil.com/correlationID=XXX`),
it will **ask you** if you really want to open this link, therefore making you aware that someone might be trying to track you,
and also providing a possibility to prevent this (by not opening the link).
2. If it recieved a link with a signature (e.g. `ext+container:name=Personal&url=https://good.com/&signature=2f7154ebeb22dd3136213aef6e385eabf63aa2d42cabc5d61beff9d52c4c5daa`).
it will check that this URL is signed by a key that is known **only to you and your local copy of this extension**.
therefore guaranteeing that this request is legit, sanctioned by you and can be opened automatically.
It is obvious that this extra step creates certain friction when using the extension, so that's why a couple of features
to mitigate the inconvenince have been added:
1. The extension now comes with a small popup that would provide you with an easy way to create secure links or terminal commands for any page you need to be opened in any container.
2. The signing mechanism uses a simple and well-known yet pretty secure HMAC-SHA256 algorithm, and the signing key is avalable in the extension UI (please be careful with it!),
therefore enabling you to easily integrate signature generation in your scripts or applications.
3. The launcher included with the extension already has a built-in support for URL signing, therefore you can pass the signing key using environment variables,
and keep using the launcher without having to deal with signatures at all (again, please be careful with the signing key!).
## Launcher
Shell launcher provides a shortcut for opening links in a more user-friendly and unix-style way.
```
$ firefox-container --help
firefox-container - open URL in a specific container in Firefox.
Usage:
firefox-container [OPTIONS] URL
firefox-container URL [OPTIONS]
firefox-container -h|--help
Where optional OPTIONS may include any combination of:
--COLOR color for the container (if does not exist)
--ICON icon for the container (if does not exist)
-n, --name=NAME container name (default: domain part of the URL)
-s, --signature=SIGNATURE container signature to prevent clickjacking; also see (1) below
-p, --pin pin tab
-r, --reader open tab in the reader mode
Where COLOR is one of:
--blue
--turquoise
--green
--yellow
--orange
--red
--pink
--purple
Where ICON is one of:
--fingerprint
--briefcase
--dollar
--cart
--circle
--gift
--vacation
--food
--fruit
--pet
--tree
--chill
Environment variables:
(1) OPEN_URL_IN_CONTAINER_SIGNING_KEY signing key for the clickjacking prevention mechanism; if set to a non-empty value, SIGNATURE will be generated by automatically
```
### Installation example
```bash
$ curl -sL https://github.com/honsiorovskyi/open-url-in-container/raw/master/launcher.sh | sudo tee /usr/bin/firefox-container > /dev/null
$ sudo chmod 0755 /usr/bin/firefox-container
```
## Build
### Step 1: Install node, npm, yarn
### Step 2:
```bash
$ git clone https://github.com/honsiorovskyi/open-url-in-container.git
$ cd open-url-in-container/build
$ yarn
$ yarn build
```
## License
[Mozilla Public License Version 2.0](LICENSE)
## Contibutions
Contibutions are very welcome. There's no specific process right now, just open your PRs/issues in this repo.
================================================
FILE: bin/launcher.sh
================================================
#!/bin/bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
usage() {
app=$(basename $0)
printf "$app - open URL in a specific container in Firefox.\n\n"
printf "Usage:\n"
printf "\t$app [OPTIONS] URL\n"
printf "\t$app URL [OPTIONS]\n"
printf "\t$app -h|--help\n\n"
printf "Where optional OPTIONS may include any combination of:\n"
printf "\t--COLOR\t\t\tcolor for the container (if does not exist)\n"
printf "\t--ICON\t\t\ticon for the container (if does not exist)\n"
printf " -n,\t--name=NAME\t\tcontainer name (default: domain part of the URL)\n"
printf " -s,\t--signature=SIGNATURE\tcontainer signature to prevent clickjacking; also see (1) below\n"
printf " -p,\t--pin\t\t\tpin tab\n"
printf " -r,\t--reader\t\topen tab in the reader mode\n\n"
printf "Where COLOR is one of:\n\t--blue\n\t--turquoise\n\t--green\n\t--yellow\n\t--orange\n\t--red\n\t--pink\n\t--purple\n\n"
printf "Where ICON is one of:\n\t--fingerprint\n\t--briefcase\n\t--dollar\n\t--cart\n\t--circle\n\t--gift\n\t--vacation\n\t--food\n\t--fruit\n\t--pet\n\t--tree\n\t--chill\n\n"
printf "Environment variables:\n"
printf " (1) \tOPEN_URL_IN_CONTAINER_SIGNING_KEY\tsigning key for the clickjacking prevention mechanism; if set to a non-empty value, SIGNATURE will be generated by automatically\n"
exit 1
}
assertOnlyOne() {
var="$1"
if [ -n "${!var}" ]
then
echo "Error: Only one $var allowed"
usage
fi
}
assertRequired() {
var="$1"
if [ -z "${!var}" ]
then
echo "Error: URL is a required parameter"
usage
fi
}
# code from: https://stackoverflow.com/questions/296536/how-to-urlencode-data-for-curl-command
urlencode() {
local string="${1}"
local strlen=${#string}
local encoded=""
local pos c o
for (( pos=0 ; pos<strlen ; pos++ )); do
c=${string:$pos:1}
case "$c" in
[-_.~a-zA-Z0-9] ) o="${c}" ;;
* ) printf -v o '%%%02x' "'$c"
esac
encoded+="${o}"
done
echo "${encoded}"
}
if [ -z "$FIREFOX" ]
then
FIREFOX=firefox
fi
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-h|--help)
usage
;;
-n|--name)
assertOnlyOne NAME
NAME="$2"
shift
shift
;;
--name=*)
assertOnlyOne NAME
_name="$1"
NAME="${_name#--name=}"
shift
;;
-s|--signature)
assertOnlyOne SIGNATURE
SIGNATURE="$2"
shift
shift
;;
--signature=*)
assertOnlyOne SIGNATURE
_signature="$1"
SIGNATURE="${_signature#--signature=}"
shift
shift
;;
--blue|--turquoise|--green|--yellow|--orange|--red|--pink|--purple)
assertOnlyOne COLOR
COLOR="${1#--}"
shift
;;
--fingerprint|--briefcase|--dollar|--cart|--circle|--gift|--vacation|--food|--fruit|--pet|--tree|--chill)
assertOnlyOne ICON
ICON="${1#--}"
shift
;;
-p|--pin)
PIN=true
shift
;;
-r|--reader)
READER_MODE=true
shift
;;
--*|-*)
echo "Error: Unknown parameter: $1"
usage
;;
*)
assertOnlyOne URL
URL="$1"
shift
;;
esac
done
assertRequired URL
if [ -z "$NAME" ]
then
NAME=${URL#*//} # strip [method:]//
NAME=${NAME%%/*} # strip path
fi
if [ -n "$OPEN_URL_IN_CONTAINER_SIGNING_KEY" -a -z "$SIGNATURE" ]
then
SIGNATURE=$(printf "${NAME}${OPEN_URL_IN_CONTAINER_SIGNING_KEY}" | openssl dgst -sha256 | cut -d " " -f 2)
fi
URL=$(urlencode $URL)
FULL_URL="ext+container:url=${URL}&name=${NAME}"
if [ -n "$COLOR" ]
then
FULL_URL="${FULL_URL}&color=${COLOR}"
fi
if [ -n "$ICON" ]
then
FULL_URL="${FULL_URL}&icon=${ICON}"
fi
if [ -n "$PIN" ]
then
FULL_URL="${FULL_URL}&pinned=true"
fi
if [ -n "$READER_MODE" ]
then
FULL_URL="${FULL_URL}&openInReaderMode=true"
fi
if [ -n "$SIGNATURE" ]
then
FULL_URL="${FULL_URL}&signature=${SIGNATURE}"
fi
$FIREFOX $FIREFOX_ARGS "$FULL_URL"
================================================
FILE: build/.eslintrc.json
================================================
{
"env": {
"browser": true,
"es6": true,
"mocha": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly",
"browser": true,
"chai": true,
"expect": true
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"indent": [
"error",
4,
{ "SwitchCase": 1 }
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
],
"no-console": [
"error",
{ "allow": ["warn", "error"]}
],
"no-await-in-loop": "error",
"no-extra-parens": "error",
"require-atomic-updates": "error",
"block-scoped-var": "error",
"consistent-return": "error",
"dot-location": [
"error",
"property"
],
"dot-notation": "error",
"no-sequences": "error",
"no-throw-literal": "error",
"no-unused-expressions": "error",
"require-await": "error"
}
}
================================================
FILE: build/package.json
================================================
{
"name": "open-url-in-container",
"author": {
"name": "Denys Honsiorovskyi",
"url": "https://github.com/honsiorovskyi"
},
"repository": {
"url": "https://github.com/honsiorovskyi/open-url-in-container"
},
"license": "MPL-2.0",
"scripts": {
"lint": "eslint -c .eslintrc.json --ignore-pattern ../src/tests/lib --fix ../src",
"build": "web-ext build --overwrite-dest --source-dir ../src --ignore-files ../src/tests",
"start": "web-ext run --source-dir ../src"
},
"devDependencies": {
"eslint": "^7.0.0",
"web-ext": "^4.2.0"
}
}
================================================
FILE: src/js/config.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const SIGNING_KEY_NAME = 'signing_key'
export const POPUP_FOLDER_STATE = 'popup_folder_state'
export const CONTAINER_SELECTOR_STATE = 'container_selector_config'
import { generateKey } from './security/keys.js'
async function setSigningKey(key) {
await browser.storage.local.set({
[SIGNING_KEY_NAME]: key
})
}
export async function regenerateSigningKey() {
await setSigningKey(await generateKey())
}
export async function getSigningKey() {
const existingKey = (await browser.storage.local.get(SIGNING_KEY_NAME))[SIGNING_KEY_NAME]
if (!existingKey) {
const newKey = await generateKey()
try {
await setSigningKey(newKey)
} catch (e) {
console.log(e) // eslint-disable-line no-console
}
return newKey
}
return existingKey
}
export async function restoreState(component, initialState = {}) {
return (await browser.storage.local.get(component))[component] || initialState
}
export async function saveState(component, state) {
await browser.storage.local.set({
[component]: state
})
}
================================================
FILE: src/js/containers.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const defaultIcon = 'fingerprint'
const availableContainerColors = [
'blue',
'turquoise',
'green',
'yellow',
'orange',
'red',
'pink',
'purple',
]
function randomColor() {
return availableContainerColors[Math.random() * availableContainerColors.length | 0]
}
async function getContainerByName(name) {
const containers = await browser.contextualIdentities.query({
name: name,
})
if (containers.length >= 1) {
return containers[0]
}
return null
}
function lookupContainer({ id, name }) {
if (id) {
return browser.contextualIdentities.get(id)
}
if (name) {
return getContainerByName(name)
}
throw new Error('looking up container: neither id, nor name is present in the params')
}
function createContainer({ name, color, icon }) {
return browser.contextualIdentities.create({
name: name,
color: color || randomColor(),
icon: icon || defaultIcon,
})
}
export async function prepareContainer({ id, name, color, icon }) {
const container = await lookupContainer({ id, name })
if (!container) {
return createContainer({ name, color, icon })
}
return container
}
================================================
FILE: src/js/opener/opener.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { getSigningKey } from '../config.js'
import { prepareContainer } from '../containers.js'
import { newTab, closeCurrentTab } from '../tabs.js'
import { SignatureError, OpenerParameters } from '../params.js'
import { parseOpenerParams } from './parser.js'
function error(e) {
console.error(e)
document.getElementById('internalErrorBody').textContent = e
document.getElementById('internalErrorContainer').classList.remove('hidden')
}
async function openTabInContainer(params) {
await newTab(await prepareContainer(params), params)
}
function requestConfirmation(params) {
document.getElementById('securityConfirmationContainerName').textContent = params.name
document.getElementById('securityConfirmationUrl').textContent = params.url
document.getElementById('securityConfirmationContainer').classList.remove('hidden')
document.getElementById('securityConfirmationConfirm').onclick = function () {
openTabInContainer(params)
}
document.getElementById('securityConfirmationGoBack').onclick = async function () {
if (window.history.length > 1) {
window.history.back()
} else {
await closeCurrentTab()
}
}
}
async function main() {
try {
// get extension parameters
const parsedParams = parseOpenerParams(window.location.hash)
const openerParams = new OpenerParameters(parsedParams)
// verify input signature to prevent clickjacking
try {
await openerParams.verify(await getSigningKey(), parsedParams.signature)
} catch (e) {
if (e instanceof SignatureError) {
// require user confirmation if signature verification failed
requestConfirmation(openerParams)
return
}
throw e
}
// finally, open a new tab
openTabInContainer(openerParams)
} catch (e) {
error(e)
return
}
}
main()
================================================
FILE: src/js/opener/parser.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import {
sanitizeURLSearchParams,
required,
url,
integer,
boolean,
atLeastOneRequired,
oneOfOrEmpty,
} from './validator.js'
const customProtocolPrefix = 'ext+container:'
const allowedContainerColors = [
'blue',
'turquoise',
'green',
'yellow',
'orange',
'red',
'pink',
'purple',
]
const allowedContainerIcons = [
'fingerprint',
'briefcase',
'dollar',
'cart',
'circle',
'gift',
'vacation',
'food',
'fruit',
'pet',
'tree',
'chill',
]
const openerParamsSchema = {
// signature
signature: [],
// container params
id: [],
name: [],
color: [oneOfOrEmpty(allowedContainerColors)],
icon: [oneOfOrEmpty(allowedContainerIcons)],
// url params
url: [required, url],
index: [integer],
pinned: [boolean],
openInReaderMode: [boolean],
// global validators
__validators: [atLeastOneRequired(['id', 'name'])],
}
export function parseOpenerParams(rawHash) {
if (rawHash[0] != '#') {
throw new Error('not a valid location hash')
}
const uri = decodeURIComponent(rawHash.substring(1))
if (!uri.startsWith(customProtocolPrefix)) {
throw new Error('unknown URI protocol')
}
const qs = new URLSearchParams(uri.substring(customProtocolPrefix.length))
return sanitizeURLSearchParams(qs, openerParamsSchema)
}
================================================
FILE: src/js/opener/validator.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
export function sanitizeURLSearchParams(qs, schema) {
let params = {}
// validate each key from the schema
// except the __validators one
for (let k of Object.keys(schema)) {
if (k === '__validators') {
continue
}
// apply each validator
let param = qs.get(k)
for (let v of schema[k]) {
param = v(param, k)
}
// skip empty params
if (isEmpty(param)) {
continue
}
params[k] = param
}
// apply global validators
for (let v of schema.__validators || []) {
params = v(params)
}
return params
}
function isEmpty(v) {
return v === null ||
v === undefined ||
v === ''
}
export function url(p) {
if (isEmpty(p)) {
return p
}
try {
return new URL(p).toString()
} catch {} // eslint-disable-line no-empty
// let's try to add 'https://' prefix and try again
p = 'https://' + p
try {
return new URL(p).toString()
} catch (e) {
throw new Error(e.message)
}
}
export function required(p, name) {
if (isEmpty(p)) {
throw new Error(`"${name}" parameter is missing`)
}
return p
}
export function integer(p, name) {
if (isEmpty(p)) {
return p
}
if (!/^[-+]?(\d+|Infinity)$/.test(p)) {
throw new Error(`"${name}" parameter should be an integer`)
}
return Number(p)
}
export function boolean(p, name) {
if (isEmpty(p)) {
return p
}
switch (p.toLowerCase()) {
case 'true':
case 'yes':
case 'on':
case '1':
return true
case 'false':
case 'no':
case 'off':
case '0':
return false
}
throw new Error(`"${name}" parameter should be a boolean (true/false, yes/no, on/off, 1/0)`)
}
export function fallback(val) {
return function(p) {
if (isEmpty(p)) {
return val
}
return p
}
}
export function oneOf(vals) {
return function(p, name) {
if (vals.indexOf(p) === -1) {
throw new Error(`"${name}" parameter should be a in a list ${vals}`)
}
return p
}
}
export function oneOfOrEmpty(vals) {
const oneOfFunc = oneOf(vals)
return function(p, name) {
if (isEmpty(p)) {
return p
}
return oneOfFunc(p, name)
}
}
export function atLeastOneRequired(requiredParams) {
return function(params) {
let valid = false
for (let p of requiredParams) {
if (!isEmpty(params[p])) {
valid = true
break
}
}
if (!valid) {
throw new Error(`at least one of "${requiredParams.join('", "')}" should be specified`)
}
return params
}
}
================================================
FILE: src/js/params.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { generateSignature, verifySignature } from './security/signature.js'
export class SignatureError extends Error { }
export class OpenerParameters {
constructor({
id,
name,
color,
icon,
url,
index,
pinned,
openInReaderMode,
}) {
this._data = {
// container properties
id: id,
name: name,
color: color,
icon: icon,
// tab properties
url: url,
index: index,
pinned: pinned,
openInReaderMode: openInReaderMode,
}
}
// container properties
get id() { return this._data.id }
get name() { return this._data.name }
get color() { return this._data.color }
get icon() { return this._data.icon }
// tab propertiese
get url() { return this._data.url }
get index() { return this._data.index }
get pinned() { return this._data.pinned }
get openInReaderMode() { return this._data.openInReaderMode }
toQueryString() {
const qs = new URLSearchParams()
for (let k of Object.keys(this._data)) {
if (this._data[k]) {
qs.set(k, this._data[k])
}
}
qs.sort()
return qs
}
async sign(key) {
const qs = this.toQueryString()
const signature = await generateSignature(key, qs.toString())
qs.set('signature', signature)
return {
queryString: qs,
signature: signature,
}
}
async verify(key, signature) {
const qs = this.toQueryString()
if (!await verifySignature(key, signature, qs.toString())) {
throw new SignatureError('signature invalid')
}
}
}
export class SignedQueryString extends URLSearchParams {
set signature(signature) {
this.set(signature)
}
get signature() {
return this.get('signature')
}
}
================================================
FILE: src/js/popup/bookmark.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { el } from './dom.js'
const LINK_ELEMENT = 'linkElement'
const LINK_ICON = 'linkIcon'
const LINK_TITLE = 'linkTitle'
const BOOKMARK_BUTTON = 'bookmarkButton'
function escape(text) {
const el = document.createElement('div')
el.innerText = text
return el.innerHTML
}
function bookmarkUrl(tab, qs) {
const url = `ext+container:${qs.toString()}`
if (!tab.favIconUrl) {
return url
}
const dataUrlMatch = tab.favIconUrl.match(/data:(.+)[;,]/)
const favIconType = dataUrlMatch ? dataUrlMatch[1] : ''
const bookmarkBody = `
<html>
<head>
<title>${escape(tab.title)}</title>
<link rel="icon" type="${favIconType}" href="${tab.favIconUrl}">
<meta http-equiv="refresh" content="0; url=${url}">
</head>
</html>
`
return `data:text/html;charset=UTF8,${encodeURIComponent(bookmarkBody)}`
}
function refreshBookmarks(url, title) {
// we have to use search by title and then filter by url manually
// because Firefox doesn't allow data URLs in the `url` field in `search` params
var existingBookmarks = browser.bookmarks.search({title: title}).then(bookmarks =>
bookmarks.filter(b => b.parentId === 'unfiled_____' && b.url == url)
)
existingBookmarks.then(bookmarks => {
if (bookmarks.length > 0) {
el(BOOKMARK_BUTTON).classList.add('exists')
} else {
el(BOOKMARK_BUTTON).classList.remove('exists')
}
})
return existingBookmarks
}
function toggleBookmark(url, title) {
refreshBookmarks(url, title).then(bookmarksFound => {
if (bookmarksFound.length == 0) {
browser.bookmarks.create({
title: title,
url: url,
}).then(refreshBookmarks.bind(this, url, title))
} else {
bookmarksFound.forEach(b => {
browser.bookmarks.remove(b.id)
.then(refreshBookmarks.bind(this, url, title))
})
}
})
}
export function updateBookmarkLink(tab, qs, containerName) {
const url = bookmarkUrl(tab, qs)
const title = `[${containerName}] ${tab.title}`
// link element
el(LINK_ELEMENT).href = url
el(LINK_ICON).style.backgroundImage = `url(${tab.favIconUrl})`
el(LINK_TITLE).textContent = tab.title
el(LINK_ELEMENT).onclick = e => {
e.preventDefault()
toggleBookmark(url, title)
}
// drag & drop handling
el(LINK_ELEMENT).onmouseenter = e => {
e.target.classList.add('hovered')
}
el(LINK_ELEMENT).onmouseleave = e => {
e.target.classList.remove('hovered')
}
el(LINK_ELEMENT).ondragstart = e => {
e.target.classList.remove('hovered')
e.target.classList.add('dragged')
}
el(LINK_ELEMENT).ondragend = e => {
e.target.classList.remove('dragged')
}
// bookmark button
refreshBookmarks(url, title)
el(BOOKMARK_BUTTON).onclick = e => {
e.preventDefault()
toggleBookmark(url, title)
}
}
================================================
FILE: src/js/popup/containers.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { getSigningKey, regenerateSigningKey } from '../config.js'
import { el, toggle, hide } from './dom.js'
const CONTAINER_ELEMENT_ID = 'container'
const CONTAINER_OPTIONS_TOGGLE = 'containerOptionsToggle'
const CONTAINER_OPTIONS = 'containerOptions'
const USE_HOSTNAME_FOR_CONTAINER_NAME = 'useHostnameForContainerName'
const USE_CONTAINER_ID = 'useContainerId'
const USE_CONTAINER_NAME = 'useContainerName'
const SIGNING_KEY_TOGGLE = 'toggleSigningKey'
const SIGNING_KEY_CONTAINER = 'signingKeyContainer'
const SIGNING_KEY = 'signingKey'
const REGENERATE_SIGNING_KEY = 'regenerateSigningKey'
const SIGNING_KEY_REGENERATION_CONFIRMATION = 'signingKeyRegenerationConfirmation'
const SIGNING_KEY_REGENERATION_CONFIRMATION_CONFIRM = 'signingKeyRegenerationConfirmationConfirm'
const SIGNING_KEY_REGENERATION_CONFIRMATION_CANCEL = 'signingKeyRegenerationConfirmationCancel'
function updateContainerList(containers, state) {
const parent = el(CONTAINER_ELEMENT_ID)
for (var i = 0; i < containers.length; i++) {
const option = document.createElement('OPTION')
option.value = containers[i].cookieStoreId
if (containers[i].cookieStoreId === state.selectedContainerId) {
option.selected = true
}
const optionName = document.createTextNode(containers[i].name)
option.appendChild(optionName)
parent.appendChild(option)
}
}
export function updateContainerOptions(state) {
el(USE_CONTAINER_ID).checked = state.useContainerId
el(USE_CONTAINER_NAME).checked = state.useContainerName
el(USE_HOSTNAME_FOR_CONTAINER_NAME).checked = state.useHostnameForContainerName
el(USE_CONTAINER_ID).disabled = state.useContainerId && !state.useContainerName
el(USE_CONTAINER_NAME).disabled = state.useContainerName && !state.useContainerId
el(CONTAINER_ELEMENT_ID).disabled = state.useHostnameForContainerName
}
async function updateSigningKey() {
el(SIGNING_KEY).value = await getSigningKey()
}
export function updateContainerSelector(containers, state) {
updateContainerList(containers, state)
updateContainerOptions(state)
}
export function setupContainerSelector(containers, s) {
el(CONTAINER_ELEMENT_ID).onchange = function (e) {
const container = containers.find(c => c.cookieStoreId === e.target.value)
s.update({ selectedContainerId: container.cookieStoreId })
}
el(USE_CONTAINER_ID).onchange = function (e) {
s.update({ useContainerId: e.target.checked })
}
el(USE_CONTAINER_NAME).onchange = function (e) {
s.update({ useContainerName: e.target.checked })
}
el(USE_HOSTNAME_FOR_CONTAINER_NAME).onchange = function (e) {
s.update({ useHostnameForContainerName: e.target.checked })
}
el(SIGNING_KEY_REGENERATION_CONFIRMATION_CONFIRM).onclick = async function () {
await regenerateSigningKey()
s.update({}) // trigger an empty update to refresh links
updateSigningKey()
hide(SIGNING_KEY_REGENERATION_CONFIRMATION)
}
el(SIGNING_KEY_REGENERATION_CONFIRMATION_CANCEL).onclick = function () {
hide(SIGNING_KEY_REGENERATION_CONFIRMATION)
}
// pure UI
el(CONTAINER_OPTIONS_TOGGLE).onclick = function () {
toggle(CONTAINER_OPTIONS)
}
el(SIGNING_KEY_TOGGLE).onclick = function () {
updateSigningKey()
toggle(SIGNING_KEY_CONTAINER)
}
el(REGENERATE_SIGNING_KEY).onclick = function () {
toggle(SIGNING_KEY_REGENERATION_CONFIRMATION)
}
}
================================================
FILE: src/js/popup/dom.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
export function hide(id) {
document.getElementById(id).classList.add('hidden')
}
export function show(id) {
document.getElementById(id).classList.remove('hidden')
}
export function toggle(id) {
document.getElementById(id).classList.toggle('hidden')
}
export function el(id) {
return document.getElementById(id)
}
================================================
FILE: src/js/popup/folders.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { saveState, restoreState, POPUP_FOLDER_STATE } from '../config.js'
import { State } from './state.js'
import { el } from './dom.js'
function updateFolderFoldingState({ newState, update }) { // eslint-disable-line no-unused-vars
saveState(POPUP_FOLDER_STATE, newState)
for (let id of Object.keys(update)) {
if (newState[id]) {
el(id).classList.remove('folded')
} else {
el(id).classList.add('folded')
}
}
}
function setupFolderFoldingListeners(s) {
const folderIds = Object.keys(s.state())
for (let i = 0; i < folderIds.length; i++) {
const folderId = folderIds[i]
el(folderId).querySelector('.title').onclick = function () {
const state = s.state()
s.update({
[folderId]: !state[folderId]
})
}
}
}
export async function setupFolderFolding() {
// update folder state
const initialState = {
containerFolder: true,
bookmarkFolder: true,
urlFolder: false,
terminalFolder: false,
signatureFolder: false,
}
const folderState = {
...initialState,
...await restoreState(POPUP_FOLDER_STATE, initialState),
}
updateFolderFoldingState({
newState: folderState,
update: folderState
})
// setup callbacks
setupFolderFoldingListeners(new State(folderState, updateFolderFoldingState))
}
================================================
FILE: src/js/popup/popup.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import {
getSigningKey,
saveState,
restoreState,
CONTAINER_SELECTOR_STATE,
} from '../config.js'
import { getActiveTab } from '../tabs.js'
import { OpenerParameters } from '../params.js'
import { State } from './state.js'
import { hide, show } from './dom.js'
import { updateBookmarkLink } from './bookmark.js'
import { updateURL } from './url.js'
import { updateTerminalCommand } from './terminal.js'
import { updateSignatureCommand } from './signature.js'
import { setupFolderFolding } from './folders.js'
import {
updateContainerSelector,
updateContainerOptions,
setupContainerSelector
} from './containers.js'
function getHostname(url) {
return new URL(url).hostname
}
async function updateLinks(containers, containerState) {
const selectedContainer = containers.find(c => c.cookieStoreId === containerState.selectedContainerId)
const tab = await getActiveTab()
const containerProps = containerState.useHostnameForContainerName ?
{
name: getHostname(tab.url),
} : {
id: containerState.useContainerId ? selectedContainer.cookieStoreId : null,
name: containerState.useContainerName ? selectedContainer.name : null,
}
const params = new OpenerParameters({
url: tab.url,
...containerProps,
})
const { queryString, signature } = await params.sign(await getSigningKey())
updateBookmarkLink(tab, queryString, containerProps.name || selectedContainer.name)
updateURL(queryString)
updateTerminalCommand(params, signature)
updateSignatureCommand(signature)
}
async function main() {
// get containers
const containers = await browser.contextualIdentities.query({})
const restoredContainerState = await restoreState(CONTAINER_SELECTOR_STATE, {
selectedContainerId: null,
useContainerId: false,
useContainerName: true,
useHostnameForContainerName: false,
})
const initialContainerState = {
...restoredContainerState,
...{
// ensure that previously selected container still exists
selectedContainerId: containers.find(c => c.cookieStoreId === restoredContainerState.selectedContainerId) ?
restoredContainerState.selectedContainerId : containers[0].cookieStoreId
}
}
// create container state manager
const containerStateManager = new State(initialContainerState, function ({ newState }) {
updateLinks(containers, newState)
updateContainerOptions(newState)
saveState(CONTAINER_SELECTOR_STATE, newState)
})
// update container select & links & commands
updateContainerSelector(containers, initialContainerState)
updateLinks(containers, initialContainerState)
// setup container selector
setupContainerSelector(containers, containerStateManager)
// setup folders & display the UI
setupFolderFolding()
hide('loader')
show('mainContainer')
}
main()
================================================
FILE: src/js/popup/signature.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { el } from './dom.js'
const SIGNATURE_INPUT_ID = 'signatureInput'
export function updateSignatureCommand(signature) {
el(SIGNATURE_INPUT_ID).value = signature
}
================================================
FILE: src/js/popup/state.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
export class State {
constructor(initialState, callback) {
this._state = initialState
this._callback = callback
}
update(update) {
const oldState = { ...this._state }
this._state = { ...this._state, ...update }
this._callback({
newState: { ...this._state },
oldState: oldState,
update: { ...update }
})
}
state() {
return { ...this._state }
}
}
================================================
FILE: src/js/popup/terminal.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { el } from './dom.js'
const TERMINAL_INPUT_ID = 'terminalInput'
export function updateTerminalCommand(params, signature) {
let propParams = []
if (params.id) {
propParams.push(`--id '${params.id}'`)
}
if (params.name) {
propParams.push(`--name '${params.name}'`)
}
el(TERMINAL_INPUT_ID).value = `firefox-container ${propParams.join(' ')} --signature '${signature}' '${params.url}'`
}
================================================
FILE: src/js/popup/url.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { el } from './dom.js'
const URL_INPUT_ID = 'urlInput'
export function updateURL(qs) {
el(URL_INPUT_ID).value = `ext+container:${qs.toString()}`
}
================================================
FILE: src/js/security/hex.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const hexEncodeArray = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
]
const hexDecodeMap = {
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'a': 0xa,
'b': 0xb,
'c': 0xc,
'd': 0xd,
'e': 0xe,
'f': 0xf,
}
export function hex2array(hex) {
if (hex.length % 2 === 1) {
throw new Error('invalid hex string length')
}
const lHex = hex.toLowerCase()
let arr = new Uint8Array(hex.length / 2)
for (let i = 0; i < lHex.length; i += 2) {
const hi = hexDecodeMap[lHex[i]]
const lo = hexDecodeMap[lHex[i+1]]
if (hi === undefined || lo === undefined) {
throw new Error('invalid character in hex string')
}
arr[i / 2] = hi << 4 | lo
}
return arr
}
export function array2hex(arr) {
var s = ''
const uintArr = new Uint8Array(arr)
for (var i = 0; i < uintArr.length; i++) {
var code = uintArr[i]
s += hexEncodeArray[code >>> 4]
s += hexEncodeArray[code & 0x0F]
}
return s
}
================================================
FILE: src/js/security/keys.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { hex2array, array2hex } from './hex.js'
async function exportKey(key) {
return array2hex(await window.crypto.subtle.exportKey('raw', key))
}
export async function importKey(rawHexKey) {
const rawKey = hex2array(rawHexKey)
return await window.crypto.subtle.importKey('raw', rawKey,
{ name: 'HMAC', 'hash': 'SHA-256', length: 256 },
true,
['sign', 'verify']
)
}
export async function generateKey() {
return await exportKey(await window.crypto.subtle.generateKey(
{ name: 'HMAC', 'hash': 'SHA-256', length: 256 },
true,
['sign', 'verify']
))
}
================================================
FILE: src/js/security/signature.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { importKey } from './keys.js'
import { hex2array, array2hex } from './hex.js'
export async function verifySignature(rawHexKey, signature, data) {
const key = await importKey(rawHexKey)
const signatureArray = hex2array(signature)
const dataArray = new TextEncoder().encode(data)
return await window.crypto.subtle.verify('HMAC', key, signatureArray, dataArray)
}
export async function generateSignature(rawHexKey, data) {
const key = await importKey(rawHexKey)
const encoder = new TextEncoder()
const dataArray = encoder.encode(data)
const signatureArray = await window.crypto.subtle.sign('HMAC', key, dataArray)
return array2hex(signatureArray)
}
================================================
FILE: src/js/tabs.js
================================================
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
export async function newTab(container, params) {
try {
let browserInfo = await browser.runtime.getBrowserInfo()
let currentTab = await browser.tabs.getCurrent()
let createTabParams = {
cookieStoreId: container.cookieStoreId,
url: params.url,
index: params.index || currentTab.index + 1,
pinned: params.pinned,
}
if (browserInfo.version >= 58) {
createTabParams.openInReaderMode = params.openInReaderMode
} else {
console.warn('openInReaderMode parameter is not supported in Firefox < 58')
}
await browser.tabs.create(createTabParams)
await browser.tabs.remove(currentTab.id)
} catch (e) {
throw new Error(`creating new tab: ${e}`)
}
}
export async function closeCurrentTab() {
let currentTab = await browser.tabs.getCurrent()
await browser.tabs.remove(currentTab.id)
}
export async function getActiveTab() {
return (await browser.tabs.query({
active: true,
windowId: browser.windows.WINDOW_ID_CURRENT
}))[0]
}
================================================
FILE: src/manifest.json
================================================
{
"manifest_version": 2,
"name": "Open links in containers",
"description": "This extension enables support for opening links in specific containers using custom protocol handler. It works for bookmars, terminal, OS shortcuts and regular HTML pages.",
"version": "2.0.0alpha10",
"browser_specific_settings": {
"gecko": {
"id": "{4d4aee45-b821-48a9-801c-d4a05821de76}"
}
},
"icons": {
"19": "icons/extension-19.png",
"48": "icons/extension-48.png",
"96": "icons/extension-96.png"
},
"protocol_handlers": [{
"protocol": "ext+container",
"name": "Open links in containers",
"uriTemplate": "/opener.html#%s"
}],
"page_action": {
"default_icon": "icons/extension-19.png",
"default_title": "Open links in containers",
"default_popup": "popup.html",
"browser_style": true,
"show_matches": ["<all_urls>"]
},
"permissions": [
"contextualIdentities",
"cookies",
"tabs",
"bookmarks",
"storage"
]
}
================================================
FILE: src/opener.html
================================================
<!-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!doctype html>
<html lang="en">
<head>
<title>Open external links in a container. Or not.</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="chrome://browser/skin/error-pages.css">
<style>
#internalErrorContainer .title {
background-image: url("chrome://global/skin/icons/error.svg");
fill: red;
}
#securityConfirmationContainer .title {
background-image: url("chrome://browser/skin/cert-error.svg");
}
.hidden {
/* opacity: 0.3; */
display: none;
}
#securityConfirmationContainerName,
#internalErrorBody,
#securityConfirmationUrl {
font-weight: bold;
}
#internalErrorBody,
#securityConfirmationUrl {
display: block;
overflow: auto;
}
</style>
</head>
<body>
<div xmlns="http://www.w3.org/1999/xhtml" id="errorPageContainer" class="container">
<div id="text-container">
<!-- Internal error -->
<div id="internalErrorContainer" class="hidden">
<div class="title">
<h1 class="title-text">Error: Open external links in a container</h1>
</div>
<div class="description">
<div>
<p>We are unable to open the URL you requested.</p>
<p>This could be due to an internal problem or some mistakes in your link. Please check the correctness of your link and try again. If you think there's a problem with an extension, please <a href="https://github.com/honsiorovskyi/open-url-in-container/issues">let us know</a>.</p>
<p>Below you can see some error details:</p>
<p id="internalErrorBody"></p>
</div>
</div>
</div>
<!-- / Internal error -->
<!-- Security confirmation -->
<div id="securityConfirmationContainer" class="hidden">
<div class="title">
<h1 class="title-text">Warning: Open external links in a container</h1>
</div>
<div class="description">
<div>
<p>
Someone, maybe you, has tried to open the following URL in your
<span id="securityConfirmationContainerName"></span> container:
</p>
<p>
<span id="securityConfirmationUrl"></span>
</p>
<p>If you do not recognize this action, maybe someone is trying to track you across your containers. In that case we recommend to go back.</p>
<p>Otherwise, if you recongnize this action and/or it was your intention, you can proceed with the redirect.</p>
</div>
</div>
<div class="button-container">
<button id="securityConfirmationGoBack" class="primary" autofocus="true">Go Back (Recommended)</button>
<button id="securityConfirmationConfirm">Continue</button>
</div>
</div>
<!-- / Security confirmation -->
</div>
<script src="./js/opener/opener.js" type="module"></script>
</body>
</html>
================================================
FILE: src/popup.html
================================================
<!-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!doctype html>
<html lang="en">
<head>
<title>Open external links in a container</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style>
body {
width: 360px;
margin: 10px;
}
input[type=checkbox] {
flex-shrink: 0;
}
.container {
padding: 5px;
}
.centered {
display: flex;
flex-direction: column;
align-items: center;
}
.hidden {
display: none;
}
.element {
padding: 5px;
}
.row {
display: flex;
flex-direction: row;
}
#containerOptions {
margin-top: 10px;
}
#container,
#signingKey {
flex-grow: 1;
}
.gear-button,
.regenerate-button {
margin-left: 5px;
width: 24px;
height: 24px;
background-size: 50%;
background-repeat: no-repeat;
background-position: 50% 50%;
}
.gear-button {
background-image: url('./icons/gear.svg');
}
.regenerate-button {
background-image: url('./icons/regenerate.svg');
}
#toggleSigningKey {
margin-bottom: 0;
}
#bookmark {
display: flex;
flex-direction: row;
padding: 5px;
border: 1px solid #CDC7C2;
background-color: #faf9f8;
}
#linkContainer {
flex-grow: 1;
min-width: 0;
overflow: hidden;
}
#linkElement {
display: flex;
flex-direction: row;
position: relative;
white-space: nowrap;
color: black;
text-decoration: none;
padding: 5px;
}
#linkIcon {
display: block;
flex-shrink: 0;
width: 20px;
height: 20px;
margin-right: 6px;
background-size: 16px 16px;
background-repeat: no-repeat;
background-position: center;
}
#linkTitle {
display: block;
line-height: 20px;
/* nice text-level overflow */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#linkElement.dragged>span {
overflow: visible;
}
#linkElement.hovered>* {
filter: blur(4px) opacity(0.6);
}
#linkElement.hovered::after {
content: 'Drag me to your bookmarks bar';
background: transparent;
display: block;
position: absolute;
float: left;
left: 0;
right: 0;
top: 0;
line-height: 30px;
text-align: center;
filter: none;
}
#bookmarkButton {
display: block;
flex-shrink: 0;
margin: 5px;
width: 20px;
height: 20px;
background-image: url('./icons/bookmark.svg');
background-size: 20px auto;
background-position: 0 0;
cursor: pointer;
}
#bookmarkButton.exists {
background-image: url('./icons/bookmark.svg');
background-size: 20px auto;
background-position: 0 20px;
}
.ro-input>input {
width: 100%;
border-radius: 0;
padding: 10px;
}
.folder.folded .content {
display: none;
}
.title {
font-weight: bold;
}
.folder > .title {
padding: 5px;
}
.folder > .title::before {
content: '–';
float: left;
width: 20px;
text-align: center;
color: #aaa;
vertical-align: middle;
}
.folder.folded > .title::before {
content: '+';
}
.folder > .content {
padding: 0;
}
</style>
</head>
<body>
<div id="loader"></div>
<div id="mainContainer" class="hidden">
<div id="containerFolder" class="folder folded">
<div class="title">Container</div>
<div class="content">
<div class="element container">
<div class="row">
<select id="container" class="browser-style"></select>
<button id="containerOptionsToggle" class="gear-button browser-style"></button>
</div>
<div class="row">
<div class="panel-formElements-item browser-style">
<input id="useHostnameForContainerName" type="checkbox" />
<label for="useHostnameForContainerName">User website domain for container name</label>
</div>
</div>
<div id="containerOptions" class="container hidden">
<div class="panel-formElements-item browser-style">
<label class="title">Container properties to include in links:</label>
</div>
<div class="panel-formElements-item browser-style">
<input id="useContainerId" type="checkbox" />
<label for="useContainerId">Container ID</label>
</div>
<div class="panel-formElements-item browser-style">
<input id="useContainerName" type="checkbox" />
<label for="useContainerName">Container Name</label>
</div>
<div>
<button id="toggleSigningKey" class="browser-style">Signing key</button>
<div id="signingKeyContainer" class="hidden">
<div>
<p><strong>Warning!</strong>
Exposing this key is potentially dangerous for your privacy.
Please be sure you know what you're doing
when using this key.
</p>
</div>
<div class="row browser-style">
<input id="signingKey" type="text" readonly class="browser-style">
<button id="regenerateSigningKey" class="regenerate-button browser-style"></button>
</div>
<div id="signingKeyRegenerationConfirmation" class="container centered hidden">
<div class="element">
<label for="buttons"><strong>Warning!</strong>
If you continue, all your previously saved links and bookmarks
will start showing an ugly warning.
Do you really want to regenerate this key?
</label>
</div>
<div class="element">
<button id="signingKeyRegenerationConfirmationConfirm"
class="browser-style default">Regenerate</button>
<button id="signingKeyRegenerationConfirmationCancel"
class="browser-style">Cancel</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="bookmarkFolder" class="folder folded">
<div class="title">Bookmark</div>
<div class="content">
<div class="element container">
<div>
<!-- this empty div is needed for dragging to work - I don't know why -->
</div>
<div id="bookmark">
<div id="linkContainer">
<a id="linkElement"><div id="linkIcon"></div><span id="linkTitle"></span></a>
</div>
<!-- <div class="tooltip">Drag me to your bookmarks bar</div> -->
<a id="bookmarkButton"></a>
</div>
</div>
</div>
</div>
<div id="urlFolder" class="folder folded">
<div class="title">URL</div>
<div class="content">
<div class="element container">
<div id="command" class="ro-input browser-style">
<input id="urlInput" type="text" readonly />
</div>
</div>
</div>
</div>
<div id="terminalFolder" class="folder folded">
<div class="title">Terminal</div>
<div class="content">
<div class="element container">
<div id="command" class="ro-input browser-style">
<input id="terminalInput" type="text" readonly />
</div>
</div>
</div>
</div>
<div id="signatureFolder" class="folder folded">
<div class="title">Signature</div>
<div class="content">
<div class="element container">
<div id="command" class="ro-input browser-style">
<input id="signatureInput" type="text" readonly />
</div>
</div>
</div>
</div>
</div>
<script src="./js/popup/popup.js" type="module"></script>
</body>
</html>
================================================
FILE: src/tests/lib/chai/chai.js
================================================
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.chai = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
module.exports = require('./lib/chai');
},{"./lib/chai":2}],2:[function(require,module,exports){
/*!
* chai
* Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
var used = [];
/*!
* Chai version
*/
exports.version = '4.2.0';
/*!
* Assertion Error
*/
exports.AssertionError = require('assertion-error');
/*!
* Utils for plugins (not exported)
*/
var util = require('./chai/utils');
/**
* # .use(function)
*
* Provides a way to extend the internals of Chai.
*
* @param {Function}
* @returns {this} for chaining
* @api public
*/
exports.use = function (fn) {
if (!~used.indexOf(fn)) {
fn(exports, util);
used.push(fn);
}
return exports;
};
/*!
* Utility Functions
*/
exports.util = util;
/*!
* Configuration
*/
var config = require('./chai/config');
exports.config = config;
/*!
* Primary `Assertion` prototype
*/
var assertion = require('./chai/assertion');
exports.use(assertion);
/*!
* Core Assertions
*/
var core = require('./chai/core/assertions');
exports.use(core);
/*!
* Expect interface
*/
var expect = require('./chai/interface/expect');
exports.use(expect);
/*!
* Should interface
*/
var should = require('./chai/interface/should');
exports.use(should);
/*!
* Assert interface
*/
var assert = require('./chai/interface/assert');
exports.use(assert);
},{"./chai/assertion":3,"./chai/config":4,"./chai/core/assertions":5,"./chai/interface/assert":6,"./chai/interface/expect":7,"./chai/interface/should":8,"./chai/utils":22,"assertion-error":33}],3:[function(require,module,exports){
/*!
* chai
* http://chaijs.com
* Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
var config = require('./config');
module.exports = function (_chai, util) {
/*!
* Module dependencies.
*/
var AssertionError = _chai.AssertionError
, flag = util.flag;
/*!
* Module export.
*/
_chai.Assertion = Assertion;
/*!
* Assertion Constructor
*
* Creates object for chaining.
*
* `Assertion` objects contain metadata in the form of flags. Three flags can
* be assigned during instantiation by passing arguments to this constructor:
*
* - `object`: This flag contains the target of the assertion. For example, in
* the assertion `expect(numKittens).to.equal(7);`, the `object` flag will
* contain `numKittens` so that the `equal` assertion can reference it when
* needed.
*
* - `message`: This flag contains an optional custom error message to be
* prepended to the error message that's generated by the assertion when it
* fails.
*
* - `ssfi`: This flag stands for "start stack function indicator". It
* contains a function reference that serves as the starting point for
* removing frames from the stack trace of the error that's created by the
* assertion when it fails. The goal is to provide a cleaner stack trace to
* end users by removing Chai's internal functions. Note that it only works
* in environments that support `Error.captureStackTrace`, and only when
* `Chai.config.includeStack` hasn't been set to `false`.
*
* - `lockSsfi`: This flag controls whether or not the given `ssfi` flag
* should retain its current value, even as assertions are chained off of
* this object. This is usually set to `true` when creating a new assertion
* from within another assertion. It's also temporarily set to `true` before
* an overwritten assertion gets called by the overwriting assertion.
*
* @param {Mixed} obj target of the assertion
* @param {String} msg (optional) custom error message
* @param {Function} ssfi (optional) starting point for removing stack frames
* @param {Boolean} lockSsfi (optional) whether or not the ssfi flag is locked
* @api private
*/
function Assertion (obj, msg, ssfi, lockSsfi) {
flag(this, 'ssfi', ssfi || Assertion);
flag(this, 'lockSsfi', lockSsfi);
flag(this, 'object', obj);
flag(this, 'message', msg);
return util.proxify(this);
}
Object.defineProperty(Assertion, 'includeStack', {
get: function() {
console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.');
return config.includeStack;
},
set: function(value) {
console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.');
config.includeStack = value;
}
});
Object.defineProperty(Assertion, 'showDiff', {
get: function() {
console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.');
return config.showDiff;
},
set: function(value) {
console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.');
config.showDiff = value;
}
});
Assertion.addProperty = function (name, fn) {
util.addProperty(this.prototype, name, fn);
};
Assertion.addMethod = function (name, fn) {
util.addMethod(this.prototype, name, fn);
};
Assertion.addChainableMethod = function (name, fn, chainingBehavior) {
util.addChainableMethod(this.prototype, name, fn, chainingBehavior);
};
Assertion.overwriteProperty = function (name, fn) {
util.overwriteProperty(this.prototype, name, fn);
};
Assertion.overwriteMethod = function (name, fn) {
util.overwriteMethod(this.prototype, name, fn);
};
Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) {
util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior);
};
/**
* ### .assert(expression, message, negateMessage, expected, actual, showDiff)
*
* Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass.
*
* @name assert
* @param {Philosophical} expression to be tested
* @param {String|Function} message or function that returns message to display if expression fails
* @param {String|Function} negatedMessage or function that returns negatedMessage to display if negated expression fails
* @param {Mixed} expected value (remember to check for negation)
* @param {Mixed} actual (optional) will default to `this.obj`
* @param {Boolean} showDiff (optional) when set to `true`, assert will display a diff in addition to the message if expression fails
* @api private
*/
Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) {
var ok = util.test(this, arguments);
if (false !== showDiff) showDiff = true;
if (undefined === expected && undefined === _actual) showDiff = false;
if (true !== config.showDiff) showDiff = false;
if (!ok) {
msg = util.getMessage(this, arguments);
var actual = util.getActual(this, arguments);
throw new AssertionError(msg, {
actual: actual
, expected: expected
, showDiff: showDiff
}, (config.includeStack) ? this.assert : flag(this, 'ssfi'));
}
};
/*!
* ### ._obj
*
* Quick reference to stored `actual` value for plugin developers.
*
* @api private
*/
Object.defineProperty(Assertion.prototype, '_obj',
{ get: function () {
return flag(this, 'object');
}
, set: function (val) {
flag(this, 'object', val);
}
});
};
},{"./config":4}],4:[function(require,module,exports){
module.exports = {
/**
* ### config.includeStack
*
* User configurable property, influences whether stack trace
* is included in Assertion error message. Default of false
* suppresses stack trace in the error message.
*
* chai.config.includeStack = true; // enable stack on error
*
* @param {Boolean}
* @api public
*/
includeStack: false,
/**
* ### config.showDiff
*
* User configurable property, influences whether or not
* the `showDiff` flag should be included in the thrown
* AssertionErrors. `false` will always be `false`; `true`
* will be true when the assertion has requested a diff
* be shown.
*
* @param {Boolean}
* @api public
*/
showDiff: true,
/**
* ### config.truncateThreshold
*
* User configurable property, sets length threshold for actual and
* expected values in assertion errors. If this threshold is exceeded, for
* example for large data structures, the value is replaced with something
* like `[ Array(3) ]` or `{ Object (prop1, prop2) }`.
*
* Set it to zero if you want to disable truncating altogether.
*
* This is especially userful when doing assertions on arrays: having this
* set to a reasonable large value makes the failure messages readily
* inspectable.
*
* chai.config.truncateThreshold = 0; // disable truncating
*
* @param {Number}
* @api public
*/
truncateThreshold: 40,
/**
* ### config.useProxy
*
* User configurable property, defines if chai will use a Proxy to throw
* an error when a non-existent property is read, which protects users
* from typos when using property-based assertions.
*
* Set it to false if you want to disable this feature.
*
* chai.config.useProxy = false; // disable use of Proxy
*
* This feature is automatically disabled regardless of this config value
* in environments that don't support proxies.
*
* @param {Boolean}
* @api public
*/
useProxy: true,
/**
* ### config.proxyExcludedKeys
*
* User configurable property, defines which properties should be ignored
* instead of throwing an error if they do not exist on the assertion.
* This is only applied if the environment Chai is running in supports proxies and
* if the `useProxy` configuration setting is enabled.
* By default, `then` and `inspect` will not throw an error if they do not exist on the
* assertion object because the `.inspect` property is read by `util.inspect` (for example, when
* using `console.log` on the assertion object) and `.then` is necessary for promise type-checking.
*
* // By default these keys will not throw an error if they do not exist on the assertion object
* chai.config.proxyExcludedKeys = ['then', 'inspect'];
*
* @param {Array}
* @api public
*/
proxyExcludedKeys: ['then', 'catch', 'inspect', 'toJSON']
};
},{}],5:[function(require,module,exports){
/*!
* chai
* http://chaijs.com
* Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
module.exports = function (chai, _) {
var Assertion = chai.Assertion
, AssertionError = chai.AssertionError
, flag = _.flag;
/**
* ### Language Chains
*
* The following are provided as chainable getters to improve the readability
* of your assertions.
*
* **Chains**
*
* - to
* - be
* - been
* - is
* - that
* - which
* - and
* - has
* - have
* - with
* - at
* - of
* - same
* - but
* - does
* - still
*
* @name language chains
* @namespace BDD
* @api public
*/
[ 'to', 'be', 'been', 'is'
, 'and', 'has', 'have', 'with'
, 'that', 'which', 'at', 'of'
, 'same', 'but', 'does', 'still' ].forEach(function (chain) {
Assertion.addProperty(chain);
});
/**
* ### .not
*
* Negates all assertions that follow in the chain.
*
* expect(function () {}).to.not.throw();
* expect({a: 1}).to.not.have.property('b');
* expect([1, 2]).to.be.an('array').that.does.not.include(3);
*
* Just because you can negate any assertion with `.not` doesn't mean you
* should. With great power comes great responsibility. It's often best to
* assert that the one expected output was produced, rather than asserting
* that one of countless unexpected outputs wasn't produced. See individual
* assertions for specific guidance.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.not.equal(1); // Not recommended
*
* @name not
* @namespace BDD
* @api public
*/
Assertion.addProperty('not', function () {
flag(this, 'negate', true);
});
/**
* ### .deep
*
* Causes all `.equal`, `.include`, `.members`, `.keys`, and `.property`
* assertions that follow in the chain to use deep equality instead of strict
* (`===`) equality. See the `deep-eql` project page for info on the deep
* equality algorithm: https://github.com/chaijs/deep-eql.
*
* // Target object deeply (but not strictly) equals `{a: 1}`
* expect({a: 1}).to.deep.equal({a: 1});
* expect({a: 1}).to.not.equal({a: 1});
*
* // Target array deeply (but not strictly) includes `{a: 1}`
* expect([{a: 1}]).to.deep.include({a: 1});
* expect([{a: 1}]).to.not.include({a: 1});
*
* // Target object deeply (but not strictly) includes `x: {a: 1}`
* expect({x: {a: 1}}).to.deep.include({x: {a: 1}});
* expect({x: {a: 1}}).to.not.include({x: {a: 1}});
*
* // Target array deeply (but not strictly) has member `{a: 1}`
* expect([{a: 1}]).to.have.deep.members([{a: 1}]);
* expect([{a: 1}]).to.not.have.members([{a: 1}]);
*
* // Target set deeply (but not strictly) has key `{a: 1}`
* expect(new Set([{a: 1}])).to.have.deep.keys([{a: 1}]);
* expect(new Set([{a: 1}])).to.not.have.keys([{a: 1}]);
*
* // Target object deeply (but not strictly) has property `x: {a: 1}`
* expect({x: {a: 1}}).to.have.deep.property('x', {a: 1});
* expect({x: {a: 1}}).to.not.have.property('x', {a: 1});
*
* @name deep
* @namespace BDD
* @api public
*/
Assertion.addProperty('deep', function () {
flag(this, 'deep', true);
});
/**
* ### .nested
*
* Enables dot- and bracket-notation in all `.property` and `.include`
* assertions that follow in the chain.
*
* expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]');
* expect({a: {b: ['x', 'y']}}).to.nested.include({'a.b[1]': 'y'});
*
* If `.` or `[]` are part of an actual property name, they can be escaped by
* adding two backslashes before them.
*
* expect({'.a': {'[b]': 'x'}}).to.have.nested.property('\\.a.\\[b\\]');
* expect({'.a': {'[b]': 'x'}}).to.nested.include({'\\.a.\\[b\\]': 'x'});
*
* `.nested` cannot be combined with `.own`.
*
* @name nested
* @namespace BDD
* @api public
*/
Assertion.addProperty('nested', function () {
flag(this, 'nested', true);
});
/**
* ### .own
*
* Causes all `.property` and `.include` assertions that follow in the chain
* to ignore inherited properties.
*
* Object.prototype.b = 2;
*
* expect({a: 1}).to.have.own.property('a');
* expect({a: 1}).to.have.property('b');
* expect({a: 1}).to.not.have.own.property('b');
*
* expect({a: 1}).to.own.include({a: 1});
* expect({a: 1}).to.include({b: 2}).but.not.own.include({b: 2});
*
* `.own` cannot be combined with `.nested`.
*
* @name own
* @namespace BDD
* @api public
*/
Assertion.addProperty('own', function () {
flag(this, 'own', true);
});
/**
* ### .ordered
*
* Causes all `.members` assertions that follow in the chain to require that
* members be in the same order.
*
* expect([1, 2]).to.have.ordered.members([1, 2])
* .but.not.have.ordered.members([2, 1]);
*
* When `.include` and `.ordered` are combined, the ordering begins at the
* start of both arrays.
*
* expect([1, 2, 3]).to.include.ordered.members([1, 2])
* .but.not.include.ordered.members([2, 3]);
*
* @name ordered
* @namespace BDD
* @api public
*/
Assertion.addProperty('ordered', function () {
flag(this, 'ordered', true);
});
/**
* ### .any
*
* Causes all `.keys` assertions that follow in the chain to only require that
* the target have at least one of the given keys. This is the opposite of
* `.all`, which requires that the target have all of the given keys.
*
* expect({a: 1, b: 2}).to.not.have.any.keys('c', 'd');
*
* See the `.keys` doc for guidance on when to use `.any` or `.all`.
*
* @name any
* @namespace BDD
* @api public
*/
Assertion.addProperty('any', function () {
flag(this, 'any', true);
flag(this, 'all', false);
});
/**
* ### .all
*
* Causes all `.keys` assertions that follow in the chain to require that the
* target have all of the given keys. This is the opposite of `.any`, which
* only requires that the target have at least one of the given keys.
*
* expect({a: 1, b: 2}).to.have.all.keys('a', 'b');
*
* Note that `.all` is used by default when neither `.all` nor `.any` are
* added earlier in the chain. However, it's often best to add `.all` anyway
* because it improves readability.
*
* See the `.keys` doc for guidance on when to use `.any` or `.all`.
*
* @name all
* @namespace BDD
* @api public
*/
Assertion.addProperty('all', function () {
flag(this, 'all', true);
flag(this, 'any', false);
});
/**
* ### .a(type[, msg])
*
* Asserts that the target's type is equal to the given string `type`. Types
* are case insensitive. See the `type-detect` project page for info on the
* type detection algorithm: https://github.com/chaijs/type-detect.
*
* expect('foo').to.be.a('string');
* expect({a: 1}).to.be.an('object');
* expect(null).to.be.a('null');
* expect(undefined).to.be.an('undefined');
* expect(new Error).to.be.an('error');
* expect(Promise.resolve()).to.be.a('promise');
* expect(new Float32Array).to.be.a('float32array');
* expect(Symbol()).to.be.a('symbol');
*
* `.a` supports objects that have a custom type set via `Symbol.toStringTag`.
*
* var myObj = {
* [Symbol.toStringTag]: 'myCustomType'
* };
*
* expect(myObj).to.be.a('myCustomType').but.not.an('object');
*
* It's often best to use `.a` to check a target's type before making more
* assertions on the same target. That way, you avoid unexpected behavior from
* any assertion that does different things based on the target's type.
*
* expect([1, 2, 3]).to.be.an('array').that.includes(2);
* expect([]).to.be.an('array').that.is.empty;
*
* Add `.not` earlier in the chain to negate `.a`. However, it's often best to
* assert that the target is the expected type, rather than asserting that it
* isn't one of many unexpected types.
*
* expect('foo').to.be.a('string'); // Recommended
* expect('foo').to.not.be.an('array'); // Not recommended
*
* `.a` accepts an optional `msg` argument which is a custom error message to
* show when the assertion fails. The message can also be given as the second
* argument to `expect`.
*
* expect(1).to.be.a('string', 'nooo why fail??');
* expect(1, 'nooo why fail??').to.be.a('string');
*
* `.a` can also be used as a language chain to improve the readability of
* your assertions.
*
* expect({b: 2}).to.have.a.property('b');
*
* The alias `.an` can be used interchangeably with `.a`.
*
* @name a
* @alias an
* @param {String} type
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function an (type, msg) {
if (msg) flag(this, 'message', msg);
type = type.toLowerCase();
var obj = flag(this, 'object')
, article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a ';
this.assert(
type === _.type(obj).toLowerCase()
, 'expected #{this} to be ' + article + type
, 'expected #{this} not to be ' + article + type
);
}
Assertion.addChainableMethod('an', an);
Assertion.addChainableMethod('a', an);
/**
* ### .include(val[, msg])
*
* When the target is a string, `.include` asserts that the given string `val`
* is a substring of the target.
*
* expect('foobar').to.include('foo');
*
* When the target is an array, `.include` asserts that the given `val` is a
* member of the target.
*
* expect([1, 2, 3]).to.include(2);
*
* When the target is an object, `.include` asserts that the given object
* `val`'s properties are a subset of the target's properties.
*
* expect({a: 1, b: 2, c: 3}).to.include({a: 1, b: 2});
*
* When the target is a Set or WeakSet, `.include` asserts that the given `val` is a
* member of the target. SameValueZero equality algorithm is used.
*
* expect(new Set([1, 2])).to.include(2);
*
* When the target is a Map, `.include` asserts that the given `val` is one of
* the values of the target. SameValueZero equality algorithm is used.
*
* expect(new Map([['a', 1], ['b', 2]])).to.include(2);
*
* Because `.include` does different things based on the target's type, it's
* important to check the target's type before using `.include`. See the `.a`
* doc for info on testing a target's type.
*
* expect([1, 2, 3]).to.be.an('array').that.includes(2);
*
* By default, strict (`===`) equality is used to compare array members and
* object properties. Add `.deep` earlier in the chain to use deep equality
* instead (WeakSet targets are not supported). See the `deep-eql` project
* page for info on the deep equality algorithm: https://github.com/chaijs/deep-eql.
*
* // Target array deeply (but not strictly) includes `{a: 1}`
* expect([{a: 1}]).to.deep.include({a: 1});
* expect([{a: 1}]).to.not.include({a: 1});
*
* // Target object deeply (but not strictly) includes `x: {a: 1}`
* expect({x: {a: 1}}).to.deep.include({x: {a: 1}});
* expect({x: {a: 1}}).to.not.include({x: {a: 1}});
*
* By default, all of the target's properties are searched when working with
* objects. This includes properties that are inherited and/or non-enumerable.
* Add `.own` earlier in the chain to exclude the target's inherited
* properties from the search.
*
* Object.prototype.b = 2;
*
* expect({a: 1}).to.own.include({a: 1});
* expect({a: 1}).to.include({b: 2}).but.not.own.include({b: 2});
*
* Note that a target object is always only searched for `val`'s own
* enumerable properties.
*
* `.deep` and `.own` can be combined.
*
* expect({a: {b: 2}}).to.deep.own.include({a: {b: 2}});
*
* Add `.nested` earlier in the chain to enable dot- and bracket-notation when
* referencing nested properties.
*
* expect({a: {b: ['x', 'y']}}).to.nested.include({'a.b[1]': 'y'});
*
* If `.` or `[]` are part of an actual property name, they can be escaped by
* adding two backslashes before them.
*
* expect({'.a': {'[b]': 2}}).to.nested.include({'\\.a.\\[b\\]': 2});
*
* `.deep` and `.nested` can be combined.
*
* expect({a: {b: [{c: 3}]}}).to.deep.nested.include({'a.b[0]': {c: 3}});
*
* `.own` and `.nested` cannot be combined.
*
* Add `.not` earlier in the chain to negate `.include`.
*
* expect('foobar').to.not.include('taco');
* expect([1, 2, 3]).to.not.include(4);
*
* However, it's dangerous to negate `.include` when the target is an object.
* The problem is that it creates uncertain expectations by asserting that the
* target object doesn't have all of `val`'s key/value pairs but may or may
* not have some of them. It's often best to identify the exact output that's
* expected, and then write an assertion that only accepts that exact output.
*
* When the target object isn't even expected to have `val`'s keys, it's
* often best to assert exactly that.
*
* expect({c: 3}).to.not.have.any.keys('a', 'b'); // Recommended
* expect({c: 3}).to.not.include({a: 1, b: 2}); // Not recommended
*
* When the target object is expected to have `val`'s keys, it's often best to
* assert that each of the properties has its expected value, rather than
* asserting that each property doesn't have one of many unexpected values.
*
* expect({a: 3, b: 4}).to.include({a: 3, b: 4}); // Recommended
* expect({a: 3, b: 4}).to.not.include({a: 1, b: 2}); // Not recommended
*
* `.include` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect([1, 2, 3]).to.include(4, 'nooo why fail??');
* expect([1, 2, 3], 'nooo why fail??').to.include(4);
*
* `.include` can also be used as a language chain, causing all `.members` and
* `.keys` assertions that follow in the chain to require the target to be a
* superset of the expected set, rather than an identical set. Note that
* `.members` ignores duplicates in the subset when `.include` is added.
*
* // Target object's keys are a superset of ['a', 'b'] but not identical
* expect({a: 1, b: 2, c: 3}).to.include.all.keys('a', 'b');
* expect({a: 1, b: 2, c: 3}).to.not.have.all.keys('a', 'b');
*
* // Target array is a superset of [1, 2] but not identical
* expect([1, 2, 3]).to.include.members([1, 2]);
* expect([1, 2, 3]).to.not.have.members([1, 2]);
*
* // Duplicates in the subset are ignored
* expect([1, 2, 3]).to.include.members([1, 2, 2, 2]);
*
* Note that adding `.any` earlier in the chain causes the `.keys` assertion
* to ignore `.include`.
*
* // Both assertions are identical
* expect({a: 1}).to.include.any.keys('a', 'b');
* expect({a: 1}).to.have.any.keys('a', 'b');
*
* The aliases `.includes`, `.contain`, and `.contains` can be used
* interchangeably with `.include`.
*
* @name include
* @alias contain
* @alias includes
* @alias contains
* @param {Mixed} val
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function SameValueZero(a, b) {
return (_.isNaN(a) && _.isNaN(b)) || a === b;
}
function includeChainingBehavior () {
flag(this, 'contains', true);
}
function include (val, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, objType = _.type(obj).toLowerCase()
, flagMsg = flag(this, 'message')
, negate = flag(this, 'negate')
, ssfi = flag(this, 'ssfi')
, isDeep = flag(this, 'deep')
, descriptor = isDeep ? 'deep ' : '';
flagMsg = flagMsg ? flagMsg + ': ' : '';
var included = false;
switch (objType) {
case 'string':
included = obj.indexOf(val) !== -1;
break;
case 'weakset':
if (isDeep) {
throw new AssertionError(
flagMsg + 'unable to use .deep.include with WeakSet',
undefined,
ssfi
);
}
included = obj.has(val);
break;
case 'map':
var isEql = isDeep ? _.eql : SameValueZero;
obj.forEach(function (item) {
included = included || isEql(item, val);
});
break;
case 'set':
if (isDeep) {
obj.forEach(function (item) {
included = included || _.eql(item, val);
});
} else {
included = obj.has(val);
}
break;
case 'array':
if (isDeep) {
included = obj.some(function (item) {
return _.eql(item, val);
})
} else {
included = obj.indexOf(val) !== -1;
}
break;
default:
// This block is for asserting a subset of properties in an object.
// `_.expectTypes` isn't used here because `.include` should work with
// objects with a custom `@@toStringTag`.
if (val !== Object(val)) {
throw new AssertionError(
flagMsg + 'object tested must be an array, a map, an object,'
+ ' a set, a string, or a weakset, but ' + objType + ' given',
undefined,
ssfi
);
}
var props = Object.keys(val)
, firstErr = null
, numErrs = 0;
props.forEach(function (prop) {
var propAssertion = new Assertion(obj);
_.transferFlags(this, propAssertion, true);
flag(propAssertion, 'lockSsfi', true);
if (!negate || props.length === 1) {
propAssertion.property(prop, val[prop]);
return;
}
try {
propAssertion.property(prop, val[prop]);
} catch (err) {
if (!_.checkError.compatibleConstructor(err, AssertionError)) {
throw err;
}
if (firstErr === null) firstErr = err;
numErrs++;
}
}, this);
// When validating .not.include with multiple properties, we only want
// to throw an assertion error if all of the properties are included,
// in which case we throw the first property assertion error that we
// encountered.
if (negate && props.length > 1 && numErrs === props.length) {
throw firstErr;
}
return;
}
// Assert inclusion in collection or substring in a string.
this.assert(
included
, 'expected #{this} to ' + descriptor + 'include ' + _.inspect(val)
, 'expected #{this} to not ' + descriptor + 'include ' + _.inspect(val));
}
Assertion.addChainableMethod('include', include, includeChainingBehavior);
Assertion.addChainableMethod('contain', include, includeChainingBehavior);
Assertion.addChainableMethod('contains', include, includeChainingBehavior);
Assertion.addChainableMethod('includes', include, includeChainingBehavior);
/**
* ### .ok
*
* Asserts that the target is a truthy value (considered `true` in boolean context).
* However, it's often best to assert that the target is strictly (`===`) or
* deeply equal to its expected value.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.be.ok; // Not recommended
*
* expect(true).to.be.true; // Recommended
* expect(true).to.be.ok; // Not recommended
*
* Add `.not` earlier in the chain to negate `.ok`.
*
* expect(0).to.equal(0); // Recommended
* expect(0).to.not.be.ok; // Not recommended
*
* expect(false).to.be.false; // Recommended
* expect(false).to.not.be.ok; // Not recommended
*
* expect(null).to.be.null; // Recommended
* expect(null).to.not.be.ok; // Not recommended
*
* expect(undefined).to.be.undefined; // Recommended
* expect(undefined).to.not.be.ok; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(false, 'nooo why fail??').to.be.ok;
*
* @name ok
* @namespace BDD
* @api public
*/
Assertion.addProperty('ok', function () {
this.assert(
flag(this, 'object')
, 'expected #{this} to be truthy'
, 'expected #{this} to be falsy');
});
/**
* ### .true
*
* Asserts that the target is strictly (`===`) equal to `true`.
*
* expect(true).to.be.true;
*
* Add `.not` earlier in the chain to negate `.true`. However, it's often best
* to assert that the target is equal to its expected value, rather than not
* equal to `true`.
*
* expect(false).to.be.false; // Recommended
* expect(false).to.not.be.true; // Not recommended
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.true; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(false, 'nooo why fail??').to.be.true;
*
* @name true
* @namespace BDD
* @api public
*/
Assertion.addProperty('true', function () {
this.assert(
true === flag(this, 'object')
, 'expected #{this} to be true'
, 'expected #{this} to be false'
, flag(this, 'negate') ? false : true
);
});
/**
* ### .false
*
* Asserts that the target is strictly (`===`) equal to `false`.
*
* expect(false).to.be.false;
*
* Add `.not` earlier in the chain to negate `.false`. However, it's often
* best to assert that the target is equal to its expected value, rather than
* not equal to `false`.
*
* expect(true).to.be.true; // Recommended
* expect(true).to.not.be.false; // Not recommended
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.false; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(true, 'nooo why fail??').to.be.false;
*
* @name false
* @namespace BDD
* @api public
*/
Assertion.addProperty('false', function () {
this.assert(
false === flag(this, 'object')
, 'expected #{this} to be false'
, 'expected #{this} to be true'
, flag(this, 'negate') ? true : false
);
});
/**
* ### .null
*
* Asserts that the target is strictly (`===`) equal to `null`.
*
* expect(null).to.be.null;
*
* Add `.not` earlier in the chain to negate `.null`. However, it's often best
* to assert that the target is equal to its expected value, rather than not
* equal to `null`.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.null; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(42, 'nooo why fail??').to.be.null;
*
* @name null
* @namespace BDD
* @api public
*/
Assertion.addProperty('null', function () {
this.assert(
null === flag(this, 'object')
, 'expected #{this} to be null'
, 'expected #{this} not to be null'
);
});
/**
* ### .undefined
*
* Asserts that the target is strictly (`===`) equal to `undefined`.
*
* expect(undefined).to.be.undefined;
*
* Add `.not` earlier in the chain to negate `.undefined`. However, it's often
* best to assert that the target is equal to its expected value, rather than
* not equal to `undefined`.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.undefined; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(42, 'nooo why fail??').to.be.undefined;
*
* @name undefined
* @namespace BDD
* @api public
*/
Assertion.addProperty('undefined', function () {
this.assert(
undefined === flag(this, 'object')
, 'expected #{this} to be undefined'
, 'expected #{this} not to be undefined'
);
});
/**
* ### .NaN
*
* Asserts that the target is exactly `NaN`.
*
* expect(NaN).to.be.NaN;
*
* Add `.not` earlier in the chain to negate `.NaN`. However, it's often best
* to assert that the target is equal to its expected value, rather than not
* equal to `NaN`.
*
* expect('foo').to.equal('foo'); // Recommended
* expect('foo').to.not.be.NaN; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(42, 'nooo why fail??').to.be.NaN;
*
* @name NaN
* @namespace BDD
* @api public
*/
Assertion.addProperty('NaN', function () {
this.assert(
_.isNaN(flag(this, 'object'))
, 'expected #{this} to be NaN'
, 'expected #{this} not to be NaN'
);
});
/**
* ### .exist
*
* Asserts that the target is not strictly (`===`) equal to either `null` or
* `undefined`. However, it's often best to assert that the target is equal to
* its expected value.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.exist; // Not recommended
*
* expect(0).to.equal(0); // Recommended
* expect(0).to.exist; // Not recommended
*
* Add `.not` earlier in the chain to negate `.exist`.
*
* expect(null).to.be.null; // Recommended
* expect(null).to.not.exist; // Not recommended
*
* expect(undefined).to.be.undefined; // Recommended
* expect(undefined).to.not.exist; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(null, 'nooo why fail??').to.exist;
*
* @name exist
* @namespace BDD
* @api public
*/
Assertion.addProperty('exist', function () {
var val = flag(this, 'object');
this.assert(
val !== null && val !== undefined
, 'expected #{this} to exist'
, 'expected #{this} to not exist'
);
});
/**
* ### .empty
*
* When the target is a string or array, `.empty` asserts that the target's
* `length` property is strictly (`===`) equal to `0`.
*
* expect([]).to.be.empty;
* expect('').to.be.empty;
*
* When the target is a map or set, `.empty` asserts that the target's `size`
* property is strictly equal to `0`.
*
* expect(new Set()).to.be.empty;
* expect(new Map()).to.be.empty;
*
* When the target is a non-function object, `.empty` asserts that the target
* doesn't have any own enumerable properties. Properties with Symbol-based
* keys are excluded from the count.
*
* expect({}).to.be.empty;
*
* Because `.empty` does different things based on the target's type, it's
* important to check the target's type before using `.empty`. See the `.a`
* doc for info on testing a target's type.
*
* expect([]).to.be.an('array').that.is.empty;
*
* Add `.not` earlier in the chain to negate `.empty`. However, it's often
* best to assert that the target contains its expected number of values,
* rather than asserting that it's not empty.
*
* expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
* expect([1, 2, 3]).to.not.be.empty; // Not recommended
*
* expect(new Set([1, 2, 3])).to.have.property('size', 3); // Recommended
* expect(new Set([1, 2, 3])).to.not.be.empty; // Not recommended
*
* expect(Object.keys({a: 1})).to.have.lengthOf(1); // Recommended
* expect({a: 1}).to.not.be.empty; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect([1, 2, 3], 'nooo why fail??').to.be.empty;
*
* @name empty
* @namespace BDD
* @api public
*/
Assertion.addProperty('empty', function () {
var val = flag(this, 'object')
, ssfi = flag(this, 'ssfi')
, flagMsg = flag(this, 'message')
, itemsCount;
flagMsg = flagMsg ? flagMsg + ': ' : '';
switch (_.type(val).toLowerCase()) {
case 'array':
case 'string':
itemsCount = val.length;
break;
case 'map':
case 'set':
itemsCount = val.size;
break;
case 'weakmap':
case 'weakset':
throw new AssertionError(
flagMsg + '.empty was passed a weak collection',
undefined,
ssfi
);
case 'function':
var msg = flagMsg + '.empty was passed a function ' + _.getName(val);
throw new AssertionError(msg.trim(), undefined, ssfi);
default:
if (val !== Object(val)) {
throw new AssertionError(
flagMsg + '.empty was passed non-string primitive ' + _.inspect(val),
undefined,
ssfi
);
}
itemsCount = Object.keys(val).length;
}
this.assert(
0 === itemsCount
, 'expected #{this} to be empty'
, 'expected #{this} not to be empty'
);
});
/**
* ### .arguments
*
* Asserts that the target is an `arguments` object.
*
* function test () {
* expect(arguments).to.be.arguments;
* }
*
* test();
*
* Add `.not` earlier in the chain to negate `.arguments`. However, it's often
* best to assert which type the target is expected to be, rather than
* asserting that its not an `arguments` object.
*
* expect('foo').to.be.a('string'); // Recommended
* expect('foo').to.not.be.arguments; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect({}, 'nooo why fail??').to.be.arguments;
*
* The alias `.Arguments` can be used interchangeably with `.arguments`.
*
* @name arguments
* @alias Arguments
* @namespace BDD
* @api public
*/
function checkArguments () {
var obj = flag(this, 'object')
, type = _.type(obj);
this.assert(
'Arguments' === type
, 'expected #{this} to be arguments but got ' + type
, 'expected #{this} to not be arguments'
);
}
Assertion.addProperty('arguments', checkArguments);
Assertion.addProperty('Arguments', checkArguments);
/**
* ### .equal(val[, msg])
*
* Asserts that the target is strictly (`===`) equal to the given `val`.
*
* expect(1).to.equal(1);
* expect('foo').to.equal('foo');
*
* Add `.deep` earlier in the chain to use deep equality instead. See the
* `deep-eql` project page for info on the deep equality algorithm:
* https://github.com/chaijs/deep-eql.
*
* // Target object deeply (but not strictly) equals `{a: 1}`
* expect({a: 1}).to.deep.equal({a: 1});
* expect({a: 1}).to.not.equal({a: 1});
*
* // Target array deeply (but not strictly) equals `[1, 2]`
* expect([1, 2]).to.deep.equal([1, 2]);
* expect([1, 2]).to.not.equal([1, 2]);
*
* Add `.not` earlier in the chain to negate `.equal`. However, it's often
* best to assert that the target is equal to its expected value, rather than
* not equal to one of countless unexpected values.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.equal(2); // Not recommended
*
* `.equal` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(1).to.equal(2, 'nooo why fail??');
* expect(1, 'nooo why fail??').to.equal(2);
*
* The aliases `.equals` and `eq` can be used interchangeably with `.equal`.
*
* @name equal
* @alias equals
* @alias eq
* @param {Mixed} val
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertEqual (val, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
if (flag(this, 'deep')) {
var prevLockSsfi = flag(this, 'lockSsfi');
flag(this, 'lockSsfi', true);
this.eql(val);
flag(this, 'lockSsfi', prevLockSsfi);
} else {
this.assert(
val === obj
, 'expected #{this} to equal #{exp}'
, 'expected #{this} to not equal #{exp}'
, val
, this._obj
, true
);
}
}
Assertion.addMethod('equal', assertEqual);
Assertion.addMethod('equals', assertEqual);
Assertion.addMethod('eq', assertEqual);
/**
* ### .eql(obj[, msg])
*
* Asserts that the target is deeply equal to the given `obj`. See the
* `deep-eql` project page for info on the deep equality algorithm:
* https://github.com/chaijs/deep-eql.
*
* // Target object is deeply (but not strictly) equal to {a: 1}
* expect({a: 1}).to.eql({a: 1}).but.not.equal({a: 1});
*
* // Target array is deeply (but not strictly) equal to [1, 2]
* expect([1, 2]).to.eql([1, 2]).but.not.equal([1, 2]);
*
* Add `.not` earlier in the chain to negate `.eql`. However, it's often best
* to assert that the target is deeply equal to its expected value, rather
* than not deeply equal to one of countless unexpected values.
*
* expect({a: 1}).to.eql({a: 1}); // Recommended
* expect({a: 1}).to.not.eql({b: 2}); // Not recommended
*
* `.eql` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect({a: 1}).to.eql({b: 2}, 'nooo why fail??');
* expect({a: 1}, 'nooo why fail??').to.eql({b: 2});
*
* The alias `.eqls` can be used interchangeably with `.eql`.
*
* The `.deep.equal` assertion is almost identical to `.eql` but with one
* difference: `.deep.equal` causes deep equality comparisons to also be used
* for any other assertions that follow in the chain.
*
* @name eql
* @alias eqls
* @param {Mixed} obj
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertEql(obj, msg) {
if (msg) flag(this, 'message', msg);
this.assert(
_.eql(obj, flag(this, 'object'))
, 'expected #{this} to deeply equal #{exp}'
, 'expected #{this} to not deeply equal #{exp}'
, obj
, this._obj
, true
);
}
Assertion.addMethod('eql', assertEql);
Assertion.addMethod('eqls', assertEql);
/**
* ### .above(n[, msg])
*
* Asserts that the target is a number or a date greater than the given number or date `n` respectively.
* However, it's often best to assert that the target is equal to its expected
* value.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.be.above(1); // Not recommended
*
* Add `.lengthOf` earlier in the chain to assert that the target's `length`
* or `size` is greater than the given number `n`.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.have.lengthOf.above(2); // Not recommended
*
* expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
* expect([1, 2, 3]).to.have.lengthOf.above(2); // Not recommended
*
* Add `.not` earlier in the chain to negate `.above`.
*
* expect(2).to.equal(2); // Recommended
* expect(1).to.not.be.above(2); // Not recommended
*
* `.above` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(1).to.be.above(2, 'nooo why fail??');
* expect(1, 'nooo why fail??').to.be.above(2);
*
* The aliases `.gt` and `.greaterThan` can be used interchangeably with
* `.above`.
*
* @name above
* @alias gt
* @alias greaterThan
* @param {Number} n
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertAbove (n, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, doLength = flag(this, 'doLength')
, flagMsg = flag(this, 'message')
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
, ssfi = flag(this, 'ssfi')
, objType = _.type(obj).toLowerCase()
, nType = _.type(n).toLowerCase()
, errorMessage
, shouldThrow = true;
if (doLength && objType !== 'map' && objType !== 'set') {
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
}
if (!doLength && (objType === 'date' && nType !== 'date')) {
errorMessage = msgPrefix + 'the argument to above must be a date';
} else if (nType !== 'number' && (doLength || objType === 'number')) {
errorMessage = msgPrefix + 'the argument to above must be a number';
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
} else {
shouldThrow = false;
}
if (shouldThrow) {
throw new AssertionError(errorMessage, undefined, ssfi);
}
if (doLength) {
var descriptor = 'length'
, itemsCount;
if (objType === 'map' || objType === 'set') {
descriptor = 'size';
itemsCount = obj.size;
} else {
itemsCount = obj.length;
}
this.assert(
itemsCount > n
, 'expected #{this} to have a ' + descriptor + ' above #{exp} but got #{act}'
, 'expected #{this} to not have a ' + descriptor + ' above #{exp}'
, n
, itemsCount
);
} else {
this.assert(
obj > n
, 'expected #{this} to be above #{exp}'
, 'expected #{this} to be at most #{exp}'
, n
);
}
}
Assertion.addMethod('above', assertAbove);
Assertion.addMethod('gt', assertAbove);
Assertion.addMethod('greaterThan', assertAbove);
/**
* ### .least(n[, msg])
*
* Asserts that the target is a number or a date greater than or equal to the given
* number or date `n` respectively. However, it's often best to assert that the target is equal to
* its expected value.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.be.at.least(1); // Not recommended
* expect(2).to.be.at.least(2); // Not recommended
*
* Add `.lengthOf` earlier in the chain to assert that the target's `length`
* or `size` is greater than or equal to the given number `n`.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.have.lengthOf.at.least(2); // Not recommended
*
* expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
* expect([1, 2, 3]).to.have.lengthOf.at.least(2); // Not recommended
*
* Add `.not` earlier in the chain to negate `.least`.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.at.least(2); // Not recommended
*
* `.least` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(1).to.be.at.least(2, 'nooo why fail??');
* expect(1, 'nooo why fail??').to.be.at.least(2);
*
* The alias `.gte` can be used interchangeably with `.least`.
*
* @name least
* @alias gte
* @param {Number} n
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertLeast (n, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, doLength = flag(this, 'doLength')
, flagMsg = flag(this, 'message')
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
, ssfi = flag(this, 'ssfi')
, objType = _.type(obj).toLowerCase()
, nType = _.type(n).toLowerCase()
, errorMessage
, shouldThrow = true;
if (doLength && objType !== 'map' && objType !== 'set') {
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
}
if (!doLength && (objType === 'date' && nType !== 'date')) {
errorMessage = msgPrefix + 'the argument to least must be a date';
} else if (nType !== 'number' && (doLength || objType === 'number')) {
errorMessage = msgPrefix + 'the argument to least must be a number';
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
} else {
shouldThrow = false;
}
if (shouldThrow) {
throw new AssertionError(errorMessage, undefined, ssfi);
}
if (doLength) {
var descriptor = 'length'
, itemsCount;
if (objType === 'map' || objType === 'set') {
descriptor = 'size';
itemsCount = obj.size;
} else {
itemsCount = obj.length;
}
this.assert(
itemsCount >= n
, 'expected #{this} to have a ' + descriptor + ' at least #{exp} but got #{act}'
, 'expected #{this} to have a ' + descriptor + ' below #{exp}'
, n
, itemsCount
);
} else {
this.assert(
obj >= n
, 'expected #{this} to be at least #{exp}'
, 'expected #{this} to be below #{exp}'
, n
);
}
}
Assertion.addMethod('least', assertLeast);
Assertion.addMethod('gte', assertLeast);
/**
* ### .below(n[, msg])
*
* Asserts that the target is a number or a date less than the given number or date `n` respectively.
* However, it's often best to assert that the target is equal to its expected
* value.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.be.below(2); // Not recommended
*
* Add `.lengthOf` earlier in the chain to assert that the target's `length`
* or `size` is less than the given number `n`.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.have.lengthOf.below(4); // Not recommended
*
* expect([1, 2, 3]).to.have.length(3); // Recommended
* expect([1, 2, 3]).to.have.lengthOf.below(4); // Not recommended
*
* Add `.not` earlier in the chain to negate `.below`.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.not.be.below(1); // Not recommended
*
* `.below` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(2).to.be.below(1, 'nooo why fail??');
* expect(2, 'nooo why fail??').to.be.below(1);
*
* The aliases `.lt` and `.lessThan` can be used interchangeably with
* `.below`.
*
* @name below
* @alias lt
* @alias lessThan
* @param {Number} n
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertBelow (n, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, doLength = flag(this, 'doLength')
, flagMsg = flag(this, 'message')
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
, ssfi = flag(this, 'ssfi')
, objType = _.type(obj).toLowerCase()
, nType = _.type(n).toLowerCase()
, errorMessage
, shouldThrow = true;
if (doLength && objType !== 'map' && objType !== 'set') {
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
}
if (!doLength && (objType === 'date' && nType !== 'date')) {
errorMessage = msgPrefix + 'the argument to below must be a date';
} else if (nType !== 'number' && (doLength || objType === 'number')) {
errorMessage = msgPrefix + 'the argument to below must be a number';
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
} else {
shouldThrow = false;
}
if (shouldThrow) {
throw new AssertionError(errorMessage, undefined, ssfi);
}
if (doLength) {
var descriptor = 'length'
, itemsCount;
if (objType === 'map' || objType === 'set') {
descriptor = 'size';
itemsCount = obj.size;
} else {
itemsCount = obj.length;
}
this.assert(
itemsCount < n
, 'expected #{this} to have a ' + descriptor + ' below #{exp} but got #{act}'
, 'expected #{this} to not have a ' + descriptor + ' below #{exp}'
, n
, itemsCount
);
} else {
this.assert(
obj < n
, 'expected #{this} to be below #{exp}'
, 'expected #{this} to be at least #{exp}'
, n
);
}
}
Assertion.addMethod('below', assertBelow);
Assertion.addMethod('lt', assertBelow);
Assertion.addMethod('lessThan', assertBelow);
/**
* ### .most(n[, msg])
*
* Asserts that the target is a number or a date less than or equal to the given number
* or date `n` respectively. However, it's often best to assert that the target is equal to its
* expected value.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.be.at.most(2); // Not recommended
* expect(1).to.be.at.most(1); // Not recommended
*
* Add `.lengthOf` earlier in the chain to assert that the target's `length`
* or `size` is less than or equal to the given number `n`.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.have.lengthOf.at.most(4); // Not recommended
*
* expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
* expect([1, 2, 3]).to.have.lengthOf.at.most(4); // Not recommended
*
* Add `.not` earlier in the chain to negate `.most`.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.not.be.at.most(1); // Not recommended
*
* `.most` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(2).to.be.at.most(1, 'nooo why fail??');
* expect(2, 'nooo why fail??').to.be.at.most(1);
*
* The alias `.lte` can be used interchangeably with `.most`.
*
* @name most
* @alias lte
* @param {Number} n
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertMost (n, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, doLength = flag(this, 'doLength')
, flagMsg = flag(this, 'message')
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
, ssfi = flag(this, 'ssfi')
, objType = _.type(obj).toLowerCase()
, nType = _.type(n).toLowerCase()
, errorMessage
, shouldThrow = true;
if (doLength && objType !== 'map' && objType !== 'set') {
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
}
if (!doLength && (objType === 'date' && nType !== 'date')) {
errorMessage = msgPrefix + 'the argument to most must be a date';
} else if (nType !== 'number' && (doLength || objType === 'number')) {
errorMessage = msgPrefix + 'the argument to most must be a number';
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
} else {
shouldThrow = false;
}
if (shouldThrow) {
throw new AssertionError(errorMessage, undefined, ssfi);
}
if (doLength) {
var descriptor = 'length'
, itemsCount;
if (objType === 'map' || objType === 'set') {
descriptor = 'size';
itemsCount = obj.size;
} else {
itemsCount = obj.length;
}
this.assert(
itemsCount <= n
, 'expected #{this} to have a ' + descriptor + ' at most #{exp} but got #{act}'
, 'expected #{this} to have a ' + descriptor + ' above #{exp}'
, n
, itemsCount
);
} else {
this.assert(
obj <= n
, 'expected #{this} to be at most #{exp}'
, 'expected #{this} to be above #{exp}'
, n
);
}
}
Assertion.addMethod('most', assertMost);
Assertion.addMethod('lte', assertMost);
/**
* ### .within(start, finish[, msg])
*
* Asserts that the target is a number or a date greater than or equal to the given
* number or date `start`, and less than or equal to the given number or date `finish` respectively.
* However, it's often best to assert that the target is equal to its expected
* value.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.be.within(1, 3); // Not recommended
* expect(2).to.be.within(2, 3); // Not recommended
* expect(2).to.be.within(1, 2); // Not recommended
*
* Add `.lengthOf` earlier in the chain to assert that the target's `length`
* or `size` is greater than or equal to the given number `start`, and less
* than or equal to the given number `finish`.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.have.lengthOf.within(2, 4); // Not recommended
*
* expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
* expect([1, 2, 3]).to.have.lengthOf.within(2, 4); // Not recommended
*
* Add `.not` earlier in the chain to negate `.within`.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.within(2, 4); // Not recommended
*
* `.within` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect(4).to.be.within(1, 3, 'nooo why fail??');
* expect(4, 'nooo why fail??').to.be.within(1, 3);
*
* @name within
* @param {Number} start lower bound inclusive
* @param {Number} finish upper bound inclusive
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
Assertion.addMethod('within', function (start, finish, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, doLength = flag(this, 'doLength')
, flagMsg = flag(this, 'message')
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
, ssfi = flag(this, 'ssfi')
, objType = _.type(obj).toLowerCase()
, startType = _.type(start).toLowerCase()
, finishType = _.type(finish).toLowerCase()
, errorMessage
, shouldThrow = true
, range = (startType === 'date' && finishType === 'date')
? start.toUTCString() + '..' + finish.toUTCString()
: start + '..' + finish;
if (doLength && objType !== 'map' && objType !== 'set') {
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
}
if (!doLength && (objType === 'date' && (startType !== 'date' || finishType !== 'date'))) {
errorMessage = msgPrefix + 'the arguments to within must be dates';
} else if ((startType !== 'number' || finishType !== 'number') && (doLength || objType === 'number')) {
errorMessage = msgPrefix + 'the arguments to within must be numbers';
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
} else {
shouldThrow = false;
}
if (shouldThrow) {
throw new AssertionError(errorMessage, undefined, ssfi);
}
if (doLength) {
var descriptor = 'length'
, itemsCount;
if (objType === 'map' || objType === 'set') {
descriptor = 'size';
itemsCount = obj.size;
} else {
itemsCount = obj.length;
}
this.assert(
itemsCount >= start && itemsCount <= finish
, 'expected #{this} to have a ' + descriptor + ' within ' + range
, 'expected #{this} to not have a ' + descriptor + ' within ' + range
);
} else {
this.assert(
obj >= start && obj <= finish
, 'expected #{this} to be within ' + range
, 'expected #{this} to not be within ' + range
);
}
});
/**
* ### .instanceof(constructor[, msg])
*
* Asserts that the target is an instance of the given `constructor`.
*
* function Cat () { }
*
* expect(new Cat()).to.be.an.instanceof(Cat);
* expect([1, 2]).to.be.an.instanceof(Array);
*
* Add `.not` earlier in the chain to negate `.instanceof`.
*
* expect({a: 1}).to.not.be.an.instanceof(Array);
*
* `.instanceof` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect(1).to.be.an.instanceof(Array, 'nooo why fail??');
* expect(1, 'nooo why fail??').to.be.an.instanceof(Array);
*
* Due to limitations in ES5, `.instanceof` may not always work as expected
* when using a transpiler such as Babel or TypeScript. In particular, it may
* produce unexpected results when subclassing built-in object such as
* `Array`, `Error`, and `Map`. See your transpiler's docs for details:
*
* - ([Babel](https://babeljs.io/docs/usage/caveats/#classes))
* - ([TypeScript](https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work))
*
* The alias `.instanceOf` can be used interchangeably with `.instanceof`.
*
* @name instanceof
* @param {Constructor} constructor
* @param {String} msg _optional_
* @alias instanceOf
* @namespace BDD
* @api public
*/
function assertInstanceOf (constructor, msg) {
if (msg) flag(this, 'message', msg);
var target = flag(this, 'object')
var ssfi = flag(this, 'ssfi');
var flagMsg = flag(this, 'message');
try {
var isInstanceOf = target instanceof constructor;
} catch (err) {
if (err instanceof TypeError) {
flagMsg = flagMsg ? flagMsg + ': ' : '';
throw new AssertionError(
flagMsg + 'The instanceof assertion needs a constructor but '
+ _.type(constructor) + ' was given.',
undefined,
ssfi
);
}
throw err;
}
var name = _.getName(constructor);
if (name === null) {
name = 'an unnamed constructor';
}
this.assert(
isInstanceOf
, 'expected #{this} to be an instance of ' + name
, 'expected #{this} to not be an instance of ' + name
);
};
Assertion.addMethod('instanceof', assertInstanceOf);
Assertion.addMethod('instanceOf', assertInstanceOf);
/**
* ### .property(name[, val[, msg]])
*
* Asserts that the target has a property with the given key `name`.
*
* expect({a: 1}).to.have.property('a');
*
* When `val` is provided, `.property` also asserts that the property's value
* is equal to the given `val`.
*
* expect({a: 1}).to.have.property('a', 1);
*
* By default, strict (`===`) equality is used. Add `.deep` earlier in the
* chain to use deep equality instead. See the `deep-eql` project page for
* info on the deep equality algorithm: https://github.com/chaijs/deep-eql.
*
* // Target object deeply (but not strictly) has property `x: {a: 1}`
* expect({x: {a: 1}}).to.have.deep.property('x', {a: 1});
* expect({x: {a: 1}}).to.not.have.property('x', {a: 1});
*
* The target's enumerable and non-enumerable properties are always included
* in the search. By default, both own and inherited properties are included.
* Add `.own` earlier in the chain to exclude inherited properties from the
* search.
*
* Object.prototype.b = 2;
*
* expect({a: 1}).to.have.own.property('a');
* expect({a: 1}).to.have.own.property('a', 1);
* expect({a: 1}).to.have.property('b');
* expect({a: 1}).to.not.have.own.property('b');
*
* `.deep` and `.own` can be combined.
*
* expect({x: {a: 1}}).to.have.deep.own.property('x', {a: 1});
*
* Add `.nested` earlier in the chain to enable dot- and bracket-notation when
* referencing nested properties.
*
* expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]');
* expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]', 'y');
*
* If `.` or `[]` are part of an actual property name, they can be escaped by
* adding two backslashes before them.
*
* expect({'.a': {'[b]': 'x'}}).to.have.nested.property('\\.a.\\[b\\]');
*
* `.deep` and `.nested` can be combined.
*
* expect({a: {b: [{c: 3}]}})
* .to.have.deep.nested.property('a.b[0]', {c: 3});
*
* `.own` and `.nested` cannot be combined.
*
* Add `.not` earlier in the chain to negate `.property`.
*
* expect({a: 1}).to.not.have.property('b');
*
* However, it's dangerous to negate `.property` when providing `val`. The
* problem is that it creates uncertain expectations by asserting that the
* target either doesn't have a property with the given key `name`, or that it
* does have a property with the given key `name` but its value isn't equal to
* the given `val`. It's often best to identify the exact output that's
* expected, and then write an assertion that only accepts that exact output.
*
* When the target isn't expected to have a property with the given key
* `name`, it's often best to assert exactly that.
*
* expect({b: 2}).to.not.have.property('a'); // Recommended
* expect({b: 2}).to.not.have.property('a', 1); // Not recommended
*
* When the target is expected to have a property with the given key `name`,
* it's often best to assert that the property has its expected value, rather
* than asserting that it doesn't have one of many unexpected values.
*
* expect({a: 3}).to.have.property('a', 3); // Recommended
* expect({a: 3}).to.not.have.property('a', 1); // Not recommended
*
* `.property` changes the target of any assertions that follow in the chain
* to be the value of the property from the original target object.
*
* expect({a: 1}).to.have.property('a').that.is.a('number');
*
* `.property` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`. When not providing `val`, only use the
* second form.
*
* // Recommended
* expect({a: 1}).to.have.property('a', 2, 'nooo why fail??');
* expect({a: 1}, 'nooo why fail??').to.have.property('a', 2);
* expect({a: 1}, 'nooo why fail??').to.have.property('b');
*
* // Not recommended
* expect({a: 1}).to.have.property('b', undefined, 'nooo why fail??');
*
* The above assertion isn't the same thing as not providing `val`. Instead,
* it's asserting that the target object has a `b` property that's equal to
* `undefined`.
*
* The assertions `.ownProperty` and `.haveOwnProperty` can be used
* interchangeably with `.own.property`.
*
* @name property
* @param {String} name
* @param {Mixed} val (optional)
* @param {String} msg _optional_
* @returns value of property for chaining
* @namespace BDD
* @api public
*/
function assertProperty (name, val, msg) {
if (msg) flag(this, 'message', msg);
var isNested = flag(this, 'nested')
, isOwn = flag(this, 'own')
, flagMsg = flag(this, 'message')
, obj = flag(this, 'object')
, ssfi = flag(this, 'ssfi')
, nameType = typeof name;
flagMsg = flagMsg ? flagMsg + ': ' : '';
if (isNested) {
if (nameType !== 'string') {
throw new AssertionError(
flagMsg + 'the argument to property must be a string when using nested syntax',
undefined,
ssfi
);
}
} else {
if (nameType !== 'string' && nameType !== 'number' && nameType !== 'symbol') {
throw new AssertionError(
flagMsg + 'the argument to property must be a string, number, or symbol',
undefined,
ssfi
);
}
}
if (isNested && isOwn) {
throw new AssertionError(
flagMsg + 'The "nested" and "own" flags cannot be combined.',
undefined,
ssfi
);
}
if (obj === null || obj === undefined) {
throw new AssertionError(
flagMsg + 'Target cannot be null or undefined.',
undefined,
ssfi
);
}
var isDeep = flag(this, 'deep')
, negate = flag(this, 'negate')
, pathInfo = isNested ? _.getPathInfo(obj, name) : null
, value = isNested ? pathInfo.value : obj[name];
var descriptor = '';
if (isDeep) descriptor += 'deep ';
if (isOwn) descriptor += 'own ';
if (isNested) descriptor += 'nested ';
descriptor += 'property ';
var hasProperty;
if (isOwn) hasProperty = Object.prototype.hasOwnProperty.call(obj, name);
else if (isNested) hasProperty = pathInfo.exists;
else hasProperty = _.hasProperty(obj, name);
// When performing a negated assertion for both name and val, merely having
// a property with the given name isn't enough to cause the assertion to
// fail. It must both have a property with the given name, and the value of
// that property must equal the given val. Therefore, skip this assertion in
// favor of the next.
if (!negate || arguments.length === 1) {
this.assert(
hasProperty
, 'expected #{this} to have ' + descriptor + _.inspect(name)
, 'expected #{this} to not have ' + descriptor + _.inspect(name));
}
if (arguments.length > 1) {
this.assert(
hasProperty && (isDeep ? _.eql(val, value) : val === value)
, 'expected #{this} to have ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}'
, 'expected #{this} to not have ' + descriptor + _.inspect(name) + ' of #{act}'
, val
, value
);
}
flag(this, 'object', value);
}
Assertion.addMethod('property', assertProperty);
function assertOwnProperty (name, value, msg) {
flag(this, 'own', true);
assertProperty.apply(this, arguments);
}
Assertion.addMethod('ownProperty', assertOwnProperty);
Assertion.addMethod('haveOwnProperty', assertOwnProperty);
/**
* ### .ownPropertyDescriptor(name[, descriptor[, msg]])
*
* Asserts that the target has its own property descriptor with the given key
* `name`. Enumerable and non-enumerable properties are included in the
* search.
*
* expect({a: 1}).to.have.ownPropertyDescriptor('a');
*
* When `descriptor` is provided, `.ownPropertyDescriptor` also asserts that
* the property's descriptor is deeply equal to the given `descriptor`. See
* the `deep-eql` project page for info on the deep equality algorithm:
* https://github.com/chaijs/deep-eql.
*
* expect({a: 1}).to.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 1,
* });
*
* Add `.not` earlier in the chain to negate `.ownPropertyDescriptor`.
*
* expect({a: 1}).to.not.have.ownPropertyDescriptor('b');
*
* However, it's dangerous to negate `.ownPropertyDescriptor` when providing
* a `descriptor`. The problem is that it creates uncertain expectations by
* asserting that the target either doesn't have a property descriptor with
* the given key `name`, or that it does have a property descriptor with the
* given key `name` but its not deeply equal to the given `descriptor`. It's
* often best to identify the exact output that's expected, and then write an
* assertion that only accepts that exact output.
*
* When the target isn't expected to have a property descriptor with the given
* key `name`, it's often best to assert exactly that.
*
* // Recommended
* expect({b: 2}).to.not.have.ownPropertyDescriptor('a');
*
* // Not recommended
* expect({b: 2}).to.not.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 1,
* });
*
* When the target is expected to have a property descriptor with the given
* key `name`, it's often best to assert that the property has its expected
* descriptor, rather than asserting that it doesn't have one of many
* unexpected descriptors.
*
* // Recommended
* expect({a: 3}).to.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 3,
* });
*
* // Not recommended
* expect({a: 3}).to.not.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 1,
* });
*
* `.ownPropertyDescriptor` changes the target of any assertions that follow
* in the chain to be the value of the property descriptor from the original
* target object.
*
* expect({a: 1}).to.have.ownPropertyDescriptor('a')
* .that.has.property('enumerable', true);
*
* `.ownPropertyDescriptor` accepts an optional `msg` argument which is a
* custom error message to show when the assertion fails. The message can also
* be given as the second argument to `expect`. When not providing
* `descriptor`, only use the second form.
*
* // Recommended
* expect({a: 1}).to.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 2,
* }, 'nooo why fail??');
*
* // Recommended
* expect({a: 1}, 'nooo why fail??').to.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 2,
* });
*
* // Recommended
* expect({a: 1}, 'nooo why fail??').to.have.ownPropertyDescriptor('b');
*
* // Not recommended
* expect({a: 1})
* .to.have.ownPropertyDescriptor('b', undefined, 'nooo why fail??');
*
* The above assertion isn't the same thing as not providing `descriptor`.
* Instead, it's asserting that the target object has a `b` property
* descriptor that's deeply equal to `undefined`.
*
* The alias `.haveOwnPropertyDescriptor` can be used interchangeably with
* `.ownPropertyDescriptor`.
*
* @name ownPropertyDescriptor
* @alias haveOwnPropertyDescriptor
* @param {String} name
* @param {Object} descriptor _optional_
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertOwnPropertyDescriptor (name, descriptor, msg) {
if (typeof descriptor === 'string') {
msg = descriptor;
descriptor = null;
}
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
var actualDescriptor = Object.getOwnPropertyDescriptor(Object(obj), name);
if (actualDescriptor && descriptor) {
this.assert(
_.eql(descriptor, actualDescriptor)
, 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to match ' + _.inspect(descriptor) + ', got ' + _.inspect(actualDescriptor)
, 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to not match ' + _.inspect(descriptor)
, descriptor
, actualDescriptor
, true
);
} else {
this.assert(
actualDescriptor
, 'expected #{this} to have an own property descriptor for ' + _.inspect(name)
, 'expected #{this} to not have an own property descriptor for ' + _.inspect(name)
);
}
flag(this, 'object', actualDescriptor);
}
Assertion.addMethod('ownPropertyDescriptor', assertOwnPropertyDescriptor);
Assertion.addMethod('haveOwnPropertyDescriptor', assertOwnPropertyDescriptor);
/**
* ### .lengthOf(n[, msg])
*
* Asserts that the target's `length` or `size` is equal to the given number
* `n`.
*
* expect([1, 2, 3]).to.have.lengthOf(3);
* expect('foo').to.have.lengthOf(3);
* expect(new Set([1, 2, 3])).to.have.lengthOf(3);
* expect(new Map([['a', 1], ['b', 2], ['c', 3]])).to.have.lengthOf(3);
*
* Add `.not` earlier in the chain to negate `.lengthOf`. However, it's often
* best to assert that the target's `length` property is equal to its expected
* value, rather than not equal to one of many unexpected values.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.not.have.lengthOf(4); // Not recommended
*
* `.lengthOf` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect([1, 2, 3]).to.have.lengthOf(2, 'nooo why fail??');
* expect([1, 2, 3], 'nooo why fail??').to.have.lengthOf(2);
*
* `.lengthOf` can also be used as a language chain, causing all `.above`,
* `.below`, `.least`, `.most`, and `.within` assertions that follow in the
* chain to use the target's `length` property as the target. However, it's
* often best to assert that the target's `length` property is equal to its
* expected length, rather than asserting that its `length` property falls
* within some range of values.
*
* // Recommended
* expect([1, 2, 3]).to.have.lengthOf(3);
*
* // Not recommended
* expect([1, 2, 3]).to.have.lengthOf.above(2);
* expect([1, 2, 3]).to.have.lengthOf.below(4);
* expect([1, 2, 3]).to.have.lengthOf.at.least(3);
* expect([1, 2, 3]).to.have.lengthOf.at.most(3);
* expect([1, 2, 3]).to.have.lengthOf.within(2,4);
*
* Due to a compatibility issue, the alias `.length` can't be chained directly
* off of an uninvoked method such as `.a`. Therefore, `.length` can't be used
* interchangeably with `.lengthOf` in every situation. It's recommended to
* always use `.lengthOf` instead of `.length`.
*
* expect([1, 2, 3]).to.have.a.length(3); // incompatible; throws error
* expect([1, 2, 3]).to.have.a.lengthOf(3); // passes as expected
*
* @name lengthOf
* @alias length
* @param {Number} n
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertLengthChain () {
flag(this, 'doLength', true);
}
function assertLength (n, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, objType = _.type(obj).toLowerCase()
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi')
, descriptor = 'length'
, itemsCount;
switch (objType) {
case 'map':
case 'set':
descriptor = 'size';
itemsCount = obj.size;
break;
default:
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
itemsCount = obj.length;
}
this.assert(
itemsCount == n
, 'expected #{this} to have a ' + descriptor + ' of #{exp} but got #{act}'
, 'expected #{this} to not have a ' + descriptor + ' of #{act}'
, n
, itemsCount
);
}
Assertion.addChainableMethod('length', assertLength, assertLengthChain);
Assertion.addChainableMethod('lengthOf', assertLength, assertLengthChain);
/**
* ### .match(re[, msg])
*
* Asserts that the target matches the given regular expression `re`.
*
* expect('foobar').to.match(/^foo/);
*
* Add `.not` earlier in the chain to negate `.match`.
*
* expect('foobar').to.not.match(/taco/);
*
* `.match` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect('foobar').to.match(/taco/, 'nooo why fail??');
* expect('foobar', 'nooo why fail??').to.match(/taco/);
*
* The alias `.matches` can be used interchangeably with `.match`.
*
* @name match
* @alias matches
* @param {RegExp} re
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertMatch(re, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
this.assert(
re.exec(obj)
, 'expected #{this} to match ' + re
, 'expected #{this} not to match ' + re
);
}
Assertion.addMethod('match', assertMatch);
Assertion.addMethod('matches', assertMatch);
/**
* ### .string(str[, msg])
*
* Asserts that the target string contains the given substring `str`.
*
* expect('foobar').to.have.string('bar');
*
* Add `.not` earlier in the chain to negate `.string`.
*
* expect('foobar').to.not.have.string('taco');
*
* `.string` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect('foobar').to.have.string('taco', 'nooo why fail??');
* expect('foobar', 'nooo why fail??').to.have.string('taco');
*
* @name string
* @param {String} str
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
Assertion.addMethod('string', function (str, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
new Assertion(obj, flagMsg, ssfi, true).is.a('string');
this.assert(
~obj.indexOf(str)
, 'expected #{this} to contain ' + _.inspect(str)
, 'expected #{this} to not contain ' + _.inspect(str)
);
});
/**
* ### .keys(key1[, key2[, ...]])
*
* Asserts that the target object, array, map, or set has the given keys. Only
* the target's own inherited properties are included in the search.
*
* When the target is an object or array, keys can be provided as one or more
* string arguments, a single array argument, or a single object argument. In
* the latter case, only the keys in the given object matter; the values are
* ignored.
*
* expect({a: 1, b: 2}).to.have.all.keys('a', 'b');
* expect(['x', 'y']).to.have.all.keys(0, 1);
*
* expect({a: 1, b: 2}).to.have.all.keys(['a', 'b']);
* expect(['x', 'y']).to.have.all.keys([0, 1]);
*
* expect({a: 1, b: 2}).to.have.all.keys({a: 4, b: 5}); // ignore 4 and 5
* expect(['x', 'y']).to.have.all.keys({0: 4, 1: 5}); // ignore 4 and 5
*
* When the target is a map or set, each key must be provided as a separate
* argument.
*
* expect(new Map([['a', 1], ['b', 2]])).to.have.all.keys('a', 'b');
* expect(new Set(['a', 'b'])).to.have.all.keys('a', 'b');
*
* Because `.keys` does different things based on the target's type, it's
* important to check the target's type before using `.keys`. See the `.a` doc
* for info on testing a target's type.
*
* expect({a: 1, b: 2}).to.be.an('object').that.has.all.keys('a', 'b');
*
* By default, strict (`===`) equality is used to compare keys of maps and
* sets. Add `.deep` earlier in the chain to use deep equality instead. See
* the `deep-eql` project page for info on the deep equality algorithm:
* https://github.com/chaijs/deep-eql.
*
* // Target set deeply (but not strictly) has key `{a: 1}`
* expect(new Set([{a: 1}])).to.have.all.deep.keys([{a: 1}]);
* expect(new Set([{a: 1}])).to.not.have.all.keys([{a: 1}]);
*
* By default, the target must have all of the given keys and no more. Add
* `.any` earlier in the chain to only require that the target have at least
* one of the given keys. Also, add `.not` earlier in the chain to negate
* `.keys`. It's often best to add `.any` when negating `.keys`, and to use
* `.all` when asserting `.keys` without negation.
*
* When negating `.keys`, `.any` is preferred because `.not.any.keys` asserts
* exactly what's expected of the output, whereas `.not.all.keys` creates
* uncertain expectations.
*
* // Recommended; asserts that target doesn't have any of the given keys
* expect({a: 1, b: 2}).to.not.have.any.keys('c', 'd');
*
* // Not recommended; asserts that target doesn't have all of the given
* // keys but may or may not have some of them
* expect({a: 1, b: 2}).to.not.have.all.keys('c', 'd');
*
* When asserting `.keys` without negation, `.all` is preferred because
* `.all.keys` asserts exactly what's expected of the output, whereas
* `.any.keys` creates uncertain expectations.
*
* // Recommended; asserts that target has all the given keys
* expect({a: 1, b: 2}).to.have.all.keys('a', 'b');
*
* // Not recommended; asserts that target has at least one of the given
* // keys but may or may not have more of them
* expect({a: 1, b: 2}).to.have.any.keys('a', 'b');
*
* Note that `.all` is used by default when neither `.all` nor `.any` appear
* earlier in the chain. However, it's often best to add `.all` anyway because
* it improves readability.
*
* // Both assertions are identical
* expect({a: 1, b: 2}).to.have.all.keys('a', 'b'); // Recommended
* expect({a: 1, b: 2}).to.have.keys('a', 'b'); // Not recommended
*
* Add `.include` earlier in the chain to require that the target's keys be a
* superset of the expected keys, rather than identical sets.
*
* // Target object's keys are a superset of ['a', 'b'] but not identical
* expect({a: 1, b: 2, c: 3}).to.include.all.keys('a', 'b');
* expect({a: 1, b: 2, c: 3}).to.not.have.all.keys('a', 'b');
*
* However, if `.any` and `.include` are combined, only the `.any` takes
* effect. The `.include` is ignored in this case.
*
* // Both assertions are identical
* expect({a: 1}).to.have.any.keys('a', 'b');
* expect({a: 1}).to.include.any.keys('a', 'b');
*
* A custom error message can be given as the second argument to `expect`.
*
* expect({a: 1}, 'nooo why fail??').to.have.key('b');
*
* The alias `.key` can be used interchangeably with `.keys`.
*
* @name keys
* @alias key
* @param {...String|Array|Object} keys
* @namespace BDD
* @api public
*/
function assertKeys (keys) {
var obj = flag(this, 'object')
, objType = _.type(obj)
, keysType = _.type(keys)
, ssfi = flag(this, 'ssfi')
, isDeep = flag(this, 'deep')
, str
, deepStr = ''
, actual
, ok = true
, flagMsg = flag(this, 'message');
flagMsg = flagMsg ? flagMsg + ': ' : '';
var mixedArgsMsg = flagMsg + 'when testing keys against an object or an array you must give a single Array|Object|String argument or multiple String arguments';
if (objType === 'Map' || objType === 'Set') {
deepStr = isDeep ? 'deeply ' : '';
actual = [];
// Map and Set '.keys' aren't supported in IE 11. Therefore, use .forEach.
obj.forEach(function (val, key) { actual.push(key) });
if (keysType !== 'Array') {
keys = Array.prototype.slice.call(arguments);
}
} else {
actual = _.getOwnEnumerableProperties(obj);
switch (keysType) {
case 'Array':
if (arguments.length > 1) {
throw new AssertionError(mixedArgsMsg, undefined, ssfi);
}
break;
case 'Object':
if (arguments.length > 1) {
throw new AssertionError(mixedArgsMsg, undefined, ssfi);
}
keys = Object.keys(keys);
break;
default:
keys = Array.prototype.slice.call(arguments);
}
// Only stringify non-Symbols because Symbols would become "Symbol()"
keys = keys.map(function (val) {
return typeof val === 'symbol' ? val : String(val);
});
}
if (!keys.length) {
throw new AssertionError(flagMsg + 'keys required', undefined, ssfi);
}
var len = keys.length
, any = flag(this, 'any')
, all = flag(this, 'all')
, expected = keys;
if (!any && !all) {
all = true;
}
// Has any
if (any) {
ok = expected.some(function(expectedKey) {
return actual.some(function(actualKey) {
if (isDeep) {
return _.eql(expectedKey, actualKey);
} else {
return expectedKey === actualKey;
}
});
});
}
// Has all
if (all) {
ok = expected.every(function(expectedKey) {
return actual.some(function(actualKey) {
if (isDeep) {
return _.eql(expectedKey, actualKey);
} else {
return expectedKey === actualKey;
}
});
});
if (!flag(this, 'contains')) {
ok = ok && keys.length == actual.length;
}
}
// Key string
if (len > 1) {
keys = keys.map(function(key) {
return _.inspect(key);
});
var last = keys.pop();
if (all) {
str = keys.join(', ') + ', and ' + last;
}
if (any) {
str = keys.join(', ') + ', or ' + last;
}
} else {
str = _.inspect(keys[0]);
}
// Form
str = (len > 1 ? 'keys ' : 'key ') + str;
// Have / include
str = (flag(this, 'contains') ? 'contain ' : 'have ') + str;
// Assertion
this.assert(
ok
, 'expected #{this} to ' + deepStr + str
, 'expected #{this} to not ' + deepStr + str
, expected.slice(0).sort(_.compareByInspect)
, actual.sort(_.compareByInspect)
, true
);
}
Assertion.addMethod('keys', assertKeys);
Assertion.addMethod('key', assertKeys);
/**
* ### .throw([errorLike], [errMsgMatcher], [msg])
*
* When no arguments are provided, `.throw` invokes the target function and
* asserts that an error is thrown.
*
* var badFn = function () { throw new TypeError('Illegal salmon!'); };
*
* expect(badFn).to.throw();
*
* When one argument is provided, and it's an error constructor, `.throw`
* invokes the target function and asserts that an error is thrown that's an
* instance of that error constructor.
*
* var badFn = function () { throw new TypeError('Illegal salmon!'); };
*
* expect(badFn).to.throw(TypeError);
*
* When one argument is provided, and it's an error instance, `.throw` invokes
* the target function and asserts that an error is thrown that's strictly
* (`===`) equal to that error instance.
*
* var err = new TypeError('Illegal salmon!');
* var badFn = function () { throw err; };
*
* expect(badFn).to.throw(err);
*
* When one argument is provided, and it's a string, `.throw` invokes the
* target function and asserts that an error is thrown with a message that
* contains that string.
*
* var badFn = function () { throw new TypeError('Illegal salmon!'); };
*
* expect(badFn).to.throw('salmon');
*
* When one argument is provided, and it's a regular expression, `.throw`
* invokes the target function and asserts that an error is thrown with a
* message that matches that regular expression.
*
* var badFn = function () { throw new TypeError('Illegal salmon!'); };
*
* expect(badFn).to.throw(/salmon/);
*
* When two arguments are provided, and the first is an error instance or
* constructor, and the second is a string or regular expression, `.throw`
* invokes the function and asserts that an error is thrown that fulfills both
* conditions as described above.
*
* var err = new TypeError('Illegal salmon!');
* var badFn = function () { throw err; };
*
* expect(badFn).to.throw(TypeError, 'salmon');
* expect(badFn).to.throw(TypeError, /salmon/);
* expect(badFn).to.throw(err, 'salmon');
* expect(badFn).to.throw(err, /salmon/);
*
* Add `.not` earlier in the chain to negate `.throw`.
*
* var goodFn = function () {};
*
* expect(goodFn).to.not.throw();
*
* However, it's dangerous to negate `.throw` when providing any arguments.
* The problem is that it creates uncertain expectations by asserting that the
* target either doesn't throw an error, or that it throws an error but of a
* different type than the given type, or that it throws an error of the given
* type but with a message that doesn't include the given string. It's often
* best to identify the exact output that's expected, and then write an
* assertion that only accepts that exact output.
*
* When the target isn't expected to throw an error, it's often best to assert
* exactly that.
*
* var goodFn = function () {};
*
* expect(goodFn).to.not.throw(); // Recommended
* expect(goodFn).to.not.throw(ReferenceError, 'x'); // Not recommended
*
* When the target is expected to throw an error, it's often best to assert
* that the error is of its expected type, and has a message that includes an
* expected string, rather than asserting that it doesn't have one of many
* unexpected types, and doesn't have a message that includes some string.
*
* var badFn = function () { throw new TypeError('Illegal salmon!'); };
*
* expect(badFn).to.throw(TypeError, 'salmon'); // Recommended
* expect(badFn).to.not.throw(ReferenceError, 'x'); // Not recommended
*
* `.throw` changes the target of any assertions that follow in the chain to
* be the error object that's thrown.
*
* var err = new TypeError('Illegal salmon!');
* err.code = 42;
* var badFn = function () { throw err; };
*
* expect(badFn).to.throw(TypeError).with.property('code', 42);
*
* `.throw` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`. When not providing two arguments, always use
* the second form.
*
* var goodFn = function () {};
*
* expect(goodFn).to.throw(TypeError, 'x', 'nooo why fail??');
* expect(goodFn, 'nooo why fail??').to.throw();
*
* Due to limitations in ES5, `.throw` may not always work as expected when
* using a transpiler such as Babel or TypeScript. In particular, it may
* produce unexpected results when subclassing the built-in `Error` object and
* then passing the subclassed constructor to `.throw`. See your transpiler's
* docs for details:
*
* - ([Babel](https://babeljs.io/docs/usage/caveats/#classes))
* - ([TypeScript](https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work))
*
* Beware of some common mistakes when using the `throw` assertion. One common
* mistake is to accidentally invoke the function yourself instead of letting
* the `throw` assertion invoke the function for you. For example, when
* testing if a function named `fn` throws, provide `fn` instead of `fn()` as
* the target for the assertion.
*
* expect(fn).to.throw(); // Good! Tests `fn` as desired
* expect(fn()).to.throw(); // Bad! Tests result of `fn()`, not `fn`
*
* If you need to assert that your function `fn` throws when passed certain
* arguments, then wrap a call to `fn` inside of another function.
*
* expect(function () { fn(42); }).to.throw(); // Function expression
* expect(() => fn(42)).to.throw(); // ES6 arrow function
*
* Another common mistake is to provide an object method (or any stand-alone
* function that relies on `this`) as the target of the assertion. Doing so is
* problematic because the `this` context will be lost when the function is
* invoked by `.throw`; there's no way for it to know what `this` is supposed
* to be. There are two ways around this problem. One solution is to wrap the
* method or function call inside of another function. Another solution is to
* use `bind`.
*
* expect(function () { cat.meow(); }).to.throw(); // Function expression
* expect(() => cat.meow()).to.throw(); // ES6 arrow function
* expect(cat.meow.bind(cat)).to.throw(); // Bind
*
* Finally, it's worth mentioning that it's a best practice in JavaScript to
* only throw `Error` and derivatives of `Error` such as `ReferenceError`,
* `TypeError`, and user-defined objects that extend `Error`. No other type of
* value will generate a stack trace when initialized. With that said, the
* `throw` assertion does technically support any type of value being thrown,
* not just `Error` and its derivatives.
*
* The aliases `.throws` and `.Throw` can be used interchangeably with
* `.throw`.
*
* @name throw
* @alias throws
* @alias Throw
* @param {Error|ErrorConstructor} errorLike
* @param {String|RegExp} errMsgMatcher error message
* @param {String} msg _optional_
* @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
* @returns error for chaining (null if no error)
* @namespace BDD
* @api public
*/
function assertThrows (errorLike, errMsgMatcher, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, ssfi = flag(this, 'ssfi')
, flagMsg = flag(this, 'message')
, negate = flag(this, 'negate') || false;
new Assertion(obj, flagMsg, ssfi, true).is.a('function');
if (errorLike instanceof RegExp || typeof errorLike === 'string') {
errMsgMatcher = errorLike;
errorLike = null;
}
var caughtErr;
try {
obj();
} catch (err) {
caughtErr = err;
}
// If we have the negate flag enabled and at least one valid argument it means we do expect an error
// but we want it to match a given set of criteria
var everyArgIsUndefined = errorLike === undefined && errMsgMatcher === undefined;
// If we've got the negate flag enabled and both args, we should only fail if both aren't compatible
// See Issue #551 and PR #683@GitHub
var everyArgIsDefined = Boolean(errorLike && errMsgMatcher);
var errorLikeFail = false;
var errMsgMatcherFail = false;
// Checking if error was thrown
if (everyArgIsUndefined || !everyArgIsUndefined && !negate) {
// We need this to display results correctly according to their types
var errorLikeString = 'an error';
if (errorLike instanceof Error) {
errorLikeString = '#{exp}';
} else if (errorLike) {
errorLikeString = _.checkError.getConstructorName(errorLike);
}
this.assert(
caughtErr
, 'expected #{this} to throw ' + errorLikeString
, 'expected #{this} to not throw an error but #{act} was thrown'
, errorLike && errorLike.toString()
, (caughtErr instanceof Error ?
caughtErr.toString() : (typeof caughtErr === 'string' ? caughtErr : caughtErr &&
_.checkError.getConstructorName(caughtErr)))
);
}
if (errorLike && caughtErr) {
// We should compare instances only if `errorLike` is an instance of `Error`
if (errorLike instanceof Error) {
var isCompatibleInstance = _.checkError.compatibleInstance(caughtErr, errorLike);
if (isCompatibleInstance === negate) {
// These checks were created to ensure we won't fail too soon when we've got both args and a negate
// See Issue #551 and PR #683@GitHub
if (everyArgIsDefined && negate) {
errorLikeFail = true;
} else {
this.assert(
negate
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}' + (caughtErr && !negate ? ' but #{act} was thrown' : '')
, errorLike.toString()
, caughtErr.toString()
);
}
}
}
var isCompatibleConstructor = _.checkError.compatibleConstructor(caughtErr, errorLike);
if (isCompatibleConstructor === negate) {
if (everyArgIsDefined && negate) {
errorLikeFail = true;
} else {
this.assert(
negate
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}' + (caughtErr ? ' but #{act} was thrown' : '')
, (errorLike instanceof Error ? errorLike.toString() : errorLike && _.checkError.getConstructorName(errorLike))
, (caughtErr instanceof Error ? caughtErr.toString() : caughtErr && _.checkError.getConstructorName(caughtErr))
);
}
}
}
if (caughtErr && errMsgMatcher !== undefined && errMsgMatcher !== null) {
// Here we check compatible messages
var placeholder = 'including';
if (errMsgMatcher instanceof RegExp) {
placeholder = 'matching'
}
var isCompatibleMessage = _.checkError.compatibleMessage(caughtErr, errMsgMatcher);
if (isCompatibleMessage === negate) {
if (everyArgIsDefined && negate) {
errMsgMatcherFail = true;
} else {
this.assert(
negate
, 'expected #{this} to throw error ' + placeholder + ' #{exp} but got #{act}'
, 'expected #{this} to throw error not ' + placeholder + ' #{exp}'
, errMsgMatcher
, _.checkError.getMessage(caughtErr)
);
}
}
}
// If both assertions failed and both should've matched we throw an error
if (errorLikeFail && errMsgMatcherFail) {
this.assert(
negate
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}' + (caughtErr ? ' but #{act} was thrown' : '')
, (errorLike instanceof Error ? errorLike.toString() : errorLike && _.checkError.getConstructorName(errorLike))
, (caughtErr instanceof Error ? caughtErr.toString() : caughtErr && _.checkError.getConstructorName(caughtErr))
);
}
flag(this, 'object', caughtErr);
};
Assertion.addMethod('throw', assertThrows);
Assertion.addMethod('throws', assertThrows);
Assertion.addMethod('Throw', assertThrows);
/**
* ### .respondTo(method[, msg])
*
* When the target is a non-function object, `.respondTo` asserts that the
* target has a method with the given name `method`. The method can be own or
* inherited, and it can be enumerable or non-enumerable.
*
* function Cat () {}
* Cat.prototype.meow = function () {};
*
* expect(new Cat()).to.respondTo('meow');
*
* When the target is a function, `.respondTo` asserts that the target's
* `prototype` property has a method with the given name `method`. Again, the
* method can be own or inherited, and it can be enumerable or non-enumerable.
*
* function Cat () {}
* Cat.prototype.meow = function () {};
*
* expect(Cat).to.respondTo('meow');
*
* Add `.itself` earlier in the chain to force `.respondTo` to treat the
* target as a non-function object, even if it's a function. Thus, it asserts
* that the target has a method with the given name `method`, rather than
* asserting that the target's `prototype` property has a method with the
* given name `method`.
*
* function Cat () {}
* Cat.prototype.meow = function () {};
* Cat.hiss = function () {};
*
* expect(Cat).itself.to.respondTo('hiss').but.not.respondTo('meow');
*
* When not adding `.itself`, it's important to check the target's type before
* using `.respondTo`. See the `.a` doc for info on checking a target's type.
*
* function Cat () {}
* Cat.prototype.meow = function () {};
*
* expect(new Cat()).to.be.an('object').that.respondsTo('meow');
*
* Add `.not` earlier in the chain to negate `.respondTo`.
*
* function Dog () {}
* Dog.prototype.bark = function () {};
*
* expect(new Dog()).to.not.respondTo('meow');
*
* `.respondTo` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect({}).to.respondTo('meow', 'nooo why fail??');
* expect({}, 'nooo why fail??').to.respondTo('meow');
*
* The alias `.respondsTo` can be used interchangeably with `.respondTo`.
*
* @name respondTo
* @alias respondsTo
* @param {String} method
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function respondTo (method, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, itself = flag(this, 'itself')
, context = ('function' === typeof obj && !itself)
? obj.prototype[method]
: obj[method];
this.assert(
'function' === typeof context
, 'expected #{this} to respond to ' + _.inspect(method)
, 'expected #{this} to not respond to ' + _.inspect(method)
);
}
Assertion.addMethod('respondTo', respondTo);
Assertion.addMethod('respondsTo', respondTo);
/**
* ### .itself
*
* Forces all `.respondTo` assertions that follow in the chain to behave as if
* the target is a non-function object, even if it's a function. Thus, it
* causes `.respondTo` to assert that the target has a method with the given
* name, rather than asserting that the target's `prototype` property has a
* method with the given name.
*
* function Cat () {}
* Cat.prototype.meow = function () {};
* Cat.hiss = function () {};
*
* expect(Cat).itself.to.respondTo('hiss').but.not.respondTo('meow');
*
* @name itself
* @namespace BDD
* @api public
*/
Assertion.addProperty('itself', function () {
flag(this, 'itself', true);
});
/**
* ### .satisfy(matcher[, msg])
*
* Invokes the given `matcher` function with the target being passed as the
* first argument, and asserts that the value returned is truthy.
*
* expect(1).to.satisfy(function(num) {
* return num > 0;
* });
*
* Add `.not` earlier in the chain to negate `.satisfy`.
*
* expect(1).to.not.satisfy(function(num) {
* return num > 2;
* });
*
* `.satisfy` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect(1).to.satisfy(function(num) {
* return num > 2;
* }, 'nooo why fail??');
*
* expect(1, 'nooo why fail??').to.satisfy(function(num) {
* return num > 2;
* });
*
* The alias `.satisfies` can be used interchangeably with `.satisfy`.
*
* @name satisfy
* @alias satisfies
* @param {Function} matcher
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function satisfy (matcher, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
var result = matcher(obj);
this.assert(
result
, 'expected #{this} to satisfy ' + _.objDisplay(matcher)
, 'expected #{this} to not satisfy' + _.objDisplay(matcher)
, flag(this, 'negate') ? false : true
, result
);
}
Assertion.addMethod('satisfy', satisfy);
Assertion.addMethod('satisfies', satisfy);
/**
* ### .closeTo(expected, delta[, msg])
*
* Asserts that the target is a number that's within a given +/- `delta` range
* of the given number `expected`. However, it's often best to assert that the
* target is equal to its expected value.
*
* // Recommended
* expect(1.5).to.equal(1.5);
*
* // Not recommended
* expect(1.5).to.be.closeTo(1, 0.5);
* expect(1.5).to.be.closeTo(2, 0.5);
* expect(1.5).to.be.closeTo(1, 1);
*
* Add `.not` earlier in the chain to negate `.closeTo`.
*
* expect(1.5).to.equal(1.5); // Recommended
* expect(1.5).to.not.be.closeTo(3, 1); // Not recommended
*
* `.closeTo` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect(1.5).to.be.closeTo(3, 1, 'nooo why fail??');
* expect(1.5, 'nooo why fail??').to.be.closeTo(3, 1);
*
* The alias `.approximately` can be used interchangeably with `.closeTo`.
*
* @name closeTo
* @alias approximately
* @param {Number} expected
* @param {Number} delta
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function closeTo(expected, delta, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
new Assertion(obj, flagMsg, ssfi, true).is.a('number');
if (typeof expected !== 'number' || typeof delta !== 'number') {
flagMsg = flagMsg ? flagMsg + ': ' : '';
throw new AssertionError(
flagMsg + 'the arguments to closeTo or approximately must be numbers',
undefined,
ssfi
);
}
this.assert(
Math.abs(obj - expected) <= delta
, 'expected #{this} to be close to ' + expected + ' +/- ' + delta
, 'expected #{this} not to be close to ' + expected + ' +/- ' + delta
);
}
Assertion.addMethod('closeTo', closeTo);
Assertion.addMethod('approximately', closeTo);
// Note: Duplicates are ignored if testing for inclusion instead of sameness.
function isSubsetOf(subset, superset, cmp, contains, ordered) {
if (!contains) {
if (subset.length !== superset.length) return false;
superset = superset.slice();
}
return subset.every(function(elem, idx) {
if (ordered) return cmp ? cmp(elem, superset[idx]) : elem === superset[idx];
if (!cmp) {
var matchIdx = superset.indexOf(elem);
if (matchIdx === -1) return false;
// Remove match from superset so not counted twice if duplicate in subset.
if (!contains) superset.splice(matchIdx, 1);
return true;
}
return superset.some(function(elem2, matchIdx) {
if (!cmp(elem, elem2)) return false;
// Remove match from superset so not counted twice if duplicate in subset.
if (!contains) superset.splice(matchIdx, 1);
return true;
});
});
}
/**
* ### .members(set[, msg])
*
* Asserts that the target array has the same members as the given array
* `set`.
*
* expect([1, 2, 3]).to.have.members([2, 1, 3]);
* expect([1, 2, 2]).to.have.members([2, 1, 2]);
*
* By default, members are compared using strict (`===`) equality. Add `.deep`
* earlier in the chain to use deep equality instead. See the `deep-eql`
* project page for info on the deep equality algorithm:
* https://github.com/chaijs/deep-eql.
*
* // Target array deeply (but not strictly) has member `{a: 1}`
* expect([{a: 1}]).to.have.deep.members([{a: 1}]);
* expect([{a: 1}]).to.not.have.members([{a: 1}]);
*
* By default, order doesn't matter. Add `.ordered` earlier in the chain to
* require that members appear in the same order.
*
* expect([1, 2, 3]).to.have.ordered.members([1, 2, 3]);
* expect([1, 2, 3]).to.have.members([2, 1, 3])
* .but.not.ordered.members([2, 1, 3]);
*
* By default, both arrays must be the same size. Add `.include` earlier in
* the chain to require that the target's members be a superset of the
* expected members. Note that duplicates are ignored in the subset when
* `.include` is added.
*
* // Target array is a superset of [1, 2] but not identical
* expect([1, 2, 3]).to.include.members([1, 2]);
* expect([1, 2, 3]).to.not.have.members([1, 2]);
*
* // Duplicates in the subset are ignored
* expect([1, 2, 3]).to.include.members([1, 2, 2, 2]);
*
* `.deep`, `.ordered`, and `.include` can all be combined. However, if
* `.include` and `.ordered` are combined, the ordering begins at the start of
* both arrays.
*
* expect([{a: 1}, {b: 2}, {c: 3}])
* .to.include.deep.ordered.members([{a: 1}, {b: 2}])
* .but.not.include.deep.ordered.members([{b: 2}, {c: 3}]);
*
* Add `.not` earlier in the chain to negate `.members`. However, it's
* dangerous to do so. The problem is that it creates uncertain expectations
* by asserting that the target array doesn't have all of the same members as
* the given array `set` but may or may not have some of them. It's often best
* to identify the exact output that's expected, and then write an assertion
* that only accepts that exact output.
*
* expect([1, 2]).to.not.include(3).and.not.include(4); // Recommended
* expect([1, 2]).to.not.have.members([3, 4]); // Not recommended
*
* `.members` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect([1, 2]).to.have.members([1, 2, 3], 'nooo why fail??');
* expect([1, 2], 'nooo why fail??').to.have.members([1, 2, 3]);
*
* @name members
* @param {Array} set
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
Assertion.addMethod('members', function (subset, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
new Assertion(obj, flagMsg, ssfi, true).to.be.an('array');
new Assertion(subset, flagMsg, ssfi, true).to.be.an('array');
var contains = flag(this, 'contains');
var ordered = flag(this, 'ordered');
var subject, failMsg, failNegateMsg;
if (contains) {
subject = ordered ? 'an ordered superset' : 'a superset';
failMsg = 'expected #{this} to be ' + subject + ' of #{exp}';
failNegateMsg = 'expected #{this} to not be ' + subject + ' of #{exp}';
} else {
subject = ordered ? 'ordered members' : 'members';
failMsg = 'expected #{this} to have the same ' + subject + ' as #{exp}';
failNegateMsg = 'expected #{this} to not have the same ' + subject + ' as #{exp}';
}
var cmp = flag(this, 'deep') ? _.eql : undefined;
this.assert(
isSubsetOf(subset, obj, cmp, contains, ordered)
, failMsg
, failNegateMsg
, subset
, obj
, true
);
});
/**
* ### .oneOf(list[, msg])
*
* Asserts that the target is a member of the given array `list`. However,
* it's often best to assert that the target is equal to its expected value.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.be.oneOf([1, 2, 3]); // Not recommended
*
* Comparisons are performed using strict (`===`) equality.
*
* Add `.not` earlier in the chain to negate `.oneOf`.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.oneOf([2, 3, 4]); // Not recommended
*
* `.oneOf` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(1).to.be.oneOf([2, 3, 4], 'nooo why fail??');
* expect(1, 'nooo why fail??').to.be.oneOf([2, 3, 4]);
*
* @name oneOf
* @param {Array<*>} list
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function oneOf (list, msg) {
if (msg) flag(this, 'message', msg);
var expected = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
new Assertion(list, flagMsg, ssfi, true).to.be.an('array');
this.assert(
list.indexOf(expected) > -1
, 'expected #{this} to be one of #{exp}'
, 'expected #{this} to not be one of #{exp}'
, list
, expected
);
}
Assertion.addMethod('oneOf', oneOf);
/**
* ### .change(subject[, prop[, msg]])
*
* When one argument is provided, `.change` asserts that the given function
* `subject` returns a different value when it's in
gitextract_d1ti6rlb/
├── .gitignore
├── CONTRIBUTORS
├── LICENSE
├── README.md
├── bin/
│ └── launcher.sh
├── build/
│ ├── .eslintrc.json
│ └── package.json
└── src/
├── js/
│ ├── config.js
│ ├── containers.js
│ ├── opener/
│ │ ├── opener.js
│ │ ├── parser.js
│ │ └── validator.js
│ ├── params.js
│ ├── popup/
│ │ ├── bookmark.js
│ │ ├── containers.js
│ │ ├── dom.js
│ │ ├── folders.js
│ │ ├── popup.js
│ │ ├── signature.js
│ │ ├── state.js
│ │ ├── terminal.js
│ │ └── url.js
│ ├── security/
│ │ ├── hex.js
│ │ ├── keys.js
│ │ └── signature.js
│ └── tabs.js
├── manifest.json
├── opener.html
├── popup.html
└── tests/
├── lib/
│ ├── chai/
│ │ └── chai.js
│ └── mocha/
│ ├── mocha.css
│ └── mocha.js
├── opener/
│ ├── parsers.test.js
│ └── validator.test.js
├── security/
│ └── hex.test.js
├── testdata/
│ └── example-links.html
├── unit.html
└── unit.test.js
SYMBOL INDEX (579 symbols across 21 files)
FILE: src/js/config.js
constant SIGNING_KEY_NAME (line 5) | const SIGNING_KEY_NAME = 'signing_key'
constant POPUP_FOLDER_STATE (line 6) | const POPUP_FOLDER_STATE = 'popup_folder_state'
constant CONTAINER_SELECTOR_STATE (line 7) | const CONTAINER_SELECTOR_STATE = 'container_selector_config'
function setSigningKey (line 11) | async function setSigningKey(key) {
function regenerateSigningKey (line 17) | async function regenerateSigningKey() {
function getSigningKey (line 21) | async function getSigningKey() {
function restoreState (line 37) | async function restoreState(component, initialState = {}) {
function saveState (line 41) | async function saveState(component, state) {
FILE: src/js/containers.js
function randomColor (line 17) | function randomColor() {
function getContainerByName (line 22) | async function getContainerByName(name) {
function lookupContainer (line 34) | function lookupContainer({ id, name }) {
function createContainer (line 46) | function createContainer({ name, color, icon }) {
function prepareContainer (line 54) | async function prepareContainer({ id, name, color, icon }) {
FILE: src/js/opener/opener.js
function error (line 11) | function error(e) {
function openTabInContainer (line 18) | async function openTabInContainer(params) {
function requestConfirmation (line 22) | function requestConfirmation(params) {
function main (line 40) | async function main() {
FILE: src/js/opener/parser.js
function parseOpenerParams (line 63) | function parseOpenerParams(rawHash) {
FILE: src/js/opener/validator.js
function sanitizeURLSearchParams (line 5) | function sanitizeURLSearchParams(qs, schema) {
function isEmpty (line 37) | function isEmpty(v) {
function url (line 43) | function url(p) {
function required (line 61) | function required(p, name) {
function integer (line 68) | function integer(p, name) {
function boolean (line 80) | function boolean(p, name) {
function fallback (line 101) | function fallback(val) {
function oneOf (line 111) | function oneOf(vals) {
function oneOfOrEmpty (line 121) | function oneOfOrEmpty(vals) {
function atLeastOneRequired (line 132) | function atLeastOneRequired(requiredParams) {
FILE: src/js/params.js
class SignatureError (line 7) | class SignatureError extends Error { }
class OpenerParameters (line 9) | class OpenerParameters {
method constructor (line 10) | constructor({
method id (line 36) | get id() { return this._data.id }
method name (line 37) | get name() { return this._data.name }
method color (line 38) | get color() { return this._data.color }
method icon (line 39) | get icon() { return this._data.icon }
method url (line 42) | get url() { return this._data.url }
method index (line 43) | get index() { return this._data.index }
method pinned (line 44) | get pinned() { return this._data.pinned }
method openInReaderMode (line 45) | get openInReaderMode() { return this._data.openInReaderMode }
method toQueryString (line 47) | toQueryString() {
method sign (line 61) | async sign(key) {
method verify (line 72) | async verify(key, signature) {
class SignedQueryString (line 81) | class SignedQueryString extends URLSearchParams {
method signature (line 82) | set signature(signature) {
method signature (line 86) | get signature() {
FILE: src/js/popup/bookmark.js
constant LINK_ELEMENT (line 7) | const LINK_ELEMENT = 'linkElement'
constant LINK_ICON (line 8) | const LINK_ICON = 'linkIcon'
constant LINK_TITLE (line 9) | const LINK_TITLE = 'linkTitle'
constant BOOKMARK_BUTTON (line 11) | const BOOKMARK_BUTTON = 'bookmarkButton'
function escape (line 13) | function escape(text) {
function bookmarkUrl (line 19) | function bookmarkUrl(tab, qs) {
function refreshBookmarks (line 40) | function refreshBookmarks(url, title) {
function toggleBookmark (line 58) | function toggleBookmark(url, title) {
function updateBookmarkLink (line 74) | function updateBookmarkLink(tab, qs, containerName) {
FILE: src/js/popup/containers.js
constant CONTAINER_ELEMENT_ID (line 8) | const CONTAINER_ELEMENT_ID = 'container'
constant CONTAINER_OPTIONS_TOGGLE (line 9) | const CONTAINER_OPTIONS_TOGGLE = 'containerOptionsToggle'
constant CONTAINER_OPTIONS (line 10) | const CONTAINER_OPTIONS = 'containerOptions'
constant USE_HOSTNAME_FOR_CONTAINER_NAME (line 11) | const USE_HOSTNAME_FOR_CONTAINER_NAME = 'useHostnameForContainerName'
constant USE_CONTAINER_ID (line 12) | const USE_CONTAINER_ID = 'useContainerId'
constant USE_CONTAINER_NAME (line 13) | const USE_CONTAINER_NAME = 'useContainerName'
constant SIGNING_KEY_TOGGLE (line 14) | const SIGNING_KEY_TOGGLE = 'toggleSigningKey'
constant SIGNING_KEY_CONTAINER (line 15) | const SIGNING_KEY_CONTAINER = 'signingKeyContainer'
constant SIGNING_KEY (line 16) | const SIGNING_KEY = 'signingKey'
constant REGENERATE_SIGNING_KEY (line 17) | const REGENERATE_SIGNING_KEY = 'regenerateSigningKey'
constant SIGNING_KEY_REGENERATION_CONFIRMATION (line 18) | const SIGNING_KEY_REGENERATION_CONFIRMATION = 'signingKeyRegenerationCon...
constant SIGNING_KEY_REGENERATION_CONFIRMATION_CONFIRM (line 19) | const SIGNING_KEY_REGENERATION_CONFIRMATION_CONFIRM = 'signingKeyRegener...
constant SIGNING_KEY_REGENERATION_CONFIRMATION_CANCEL (line 20) | const SIGNING_KEY_REGENERATION_CONFIRMATION_CANCEL = 'signingKeyRegenera...
function updateContainerList (line 22) | function updateContainerList(containers, state) {
function updateContainerOptions (line 39) | function updateContainerOptions(state) {
function updateSigningKey (line 51) | async function updateSigningKey() {
function updateContainerSelector (line 55) | function updateContainerSelector(containers, state) {
function setupContainerSelector (line 60) | function setupContainerSelector(containers, s) {
FILE: src/js/popup/dom.js
function hide (line 5) | function hide(id) {
function show (line 9) | function show(id) {
function toggle (line 13) | function toggle(id) {
function el (line 17) | function el(id) {
FILE: src/js/popup/folders.js
function updateFolderFoldingState (line 9) | function updateFolderFoldingState({ newState, update }) { // eslint-disa...
function setupFolderFoldingListeners (line 21) | function setupFolderFoldingListeners(s) {
function setupFolderFolding (line 34) | async function setupFolderFolding() {
FILE: src/js/popup/popup.js
function getHostname (line 29) | function getHostname(url) {
function updateLinks (line 33) | async function updateLinks(containers, containerState) {
function main (line 57) | async function main() {
FILE: src/js/popup/signature.js
constant SIGNATURE_INPUT_ID (line 7) | const SIGNATURE_INPUT_ID = 'signatureInput'
function updateSignatureCommand (line 9) | function updateSignatureCommand(signature) {
FILE: src/js/popup/state.js
class State (line 5) | class State {
method constructor (line 6) | constructor(initialState, callback) {
method update (line 11) | update(update) {
method state (line 21) | state() {
FILE: src/js/popup/terminal.js
constant TERMINAL_INPUT_ID (line 7) | const TERMINAL_INPUT_ID = 'terminalInput'
function updateTerminalCommand (line 9) | function updateTerminalCommand(params, signature) {
FILE: src/js/popup/url.js
constant URL_INPUT_ID (line 7) | const URL_INPUT_ID = 'urlInput'
function updateURL (line 9) | function updateURL(qs) {
FILE: src/js/security/hex.js
function hex2array (line 28) | function hex2array(hex) {
function array2hex (line 48) | function array2hex(arr) {
FILE: src/js/security/keys.js
function exportKey (line 7) | async function exportKey(key) {
function importKey (line 11) | async function importKey(rawHexKey) {
function generateKey (line 20) | async function generateKey() {
FILE: src/js/security/signature.js
function verifySignature (line 8) | async function verifySignature(rawHexKey, signature, data) {
function generateSignature (line 15) | async function generateSignature(rawHexKey, data) {
FILE: src/js/tabs.js
function newTab (line 5) | async function newTab(container, params) {
function closeCurrentTab (line 30) | async function closeCurrentTab() {
function getActiveTab (line 35) | async function getActiveTab() {
FILE: src/tests/lib/chai/chai.js
function r (line 1) | function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==...
function Assertion (line 160) | function Assertion (obj, msg, ssfi, lockSsfi) {
function an (line 661) | function an (type, msg) {
function SameValueZero (line 824) | function SameValueZero(a, b) {
function includeChainingBehavior (line 828) | function includeChainingBehavior () {
function include (line 832) | function include (val, msg) {
function checkArguments (line 1320) | function checkArguments () {
function assertEqual (line 1378) | function assertEqual (val, msg) {
function assertEql (line 1443) | function assertEql(obj, msg) {
function assertAbove (line 1501) | function assertAbove (n, msg) {
function assertLeast (line 1604) | function assertLeast (n, msg) {
function assertBelow (line 1707) | function assertBelow (n, msg) {
function assertMost (line 1810) | function assertMost (n, msg) {
function assertInstanceOf (line 2010) | function assertInstanceOf (constructor, msg) {
function assertProperty (line 2159) | function assertProperty (name, val, msg) {
function assertOwnProperty (line 2248) | function assertOwnProperty (name, value, msg) {
function assertOwnPropertyDescriptor (line 2375) | function assertOwnPropertyDescriptor (name, descriptor, msg) {
function assertLengthChain (line 2463) | function assertLengthChain () {
function assertLength (line 2467) | function assertLength (n, msg) {
function assertMatch (line 2526) | function assertMatch(re, msg) {
function assertKeys (line 2682) | function assertKeys (keys) {
function assertThrows (line 2976) | function assertThrows (errorLike, errMsgMatcher, msg) {
function respondTo (line 3171) | function respondTo (method, msg) {
function satisfy (line 3251) | function satisfy (matcher, msg) {
function closeTo (line 3305) | function closeTo(expected, delta, msg) {
function isSubsetOf (line 3332) | function isSubsetOf(subset, superset, cmp, contains, ordered) {
function oneOf (line 3495) | function oneOf (list, msg) {
function assertChanges (line 3608) | function assertChanges (subject, prop, msg) {
function assertIncreases (line 3725) | function assertIncreases (subject, prop, msg) {
function assertDecreases (line 3844) | function assertDecreases (subject, prop, msg) {
function assertDelta (line 3950) | function assertDelta(delta, msg) {
function loadShould (line 7341) | function loadShould () {
function addProperty (line 8217) | function addProperty(property) {
function inspect (line 8432) | function inspect(obj, showHidden, depth, colors) {
function formatValue (line 8454) | function formatValue(ctx, value, recurseTimes) {
function formatPrimitive (line 8605) | function formatPrimitive(ctx, value) {
function formatError (line 8634) | function formatError(value) {
function formatArray (line 8638) | function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
function formatTypedArray (line 8658) | function formatTypedArray(value) {
function formatProperty (line 8678) | function formatProperty(ctx, value, recurseTimes, visibleKeys, key, arra...
function reduceToSingleString (line 8740) | function reduceToSingleString(output, base, braces) {
function isTypedArray (line 8757) | function isTypedArray(ar) {
function isArray (line 8763) | function isArray(ar) {
function isRegExp (line 8768) | function isRegExp(re) {
function isDate (line 8772) | function isDate(d) {
function isError (line 8776) | function isError(e) {
function objectToString (line 8780) | function objectToString(o) {
function isNaN (line 8803) | function isNaN(value) {
function stringDistanceCapped (line 9262) | function stringDistanceCapped(strA, strB, cap) {
function exclude (line 9391) | function exclude () {
function AssertionError (line 9430) | function AssertionError (message, _props, ssf) {
function compatibleInstance (line 9524) | function compatibleInstance(thrown, errorLike) {
function compatibleConstructor (line 9544) | function compatibleConstructor(thrown, errorLike) {
function compatibleMessage (line 9570) | function compatibleMessage(thrown, errMatcher) {
function getFunctionName (line 9594) | function getFunctionName(constructorFn) {
function getConstructorName (line 9620) | function getConstructorName(errorLike) {
function getMessage (line 9648) | function getMessage(errorLike) {
function FakeMap (line 9677) | function FakeMap() {
function memoizeCompare (line 9704) | function memoizeCompare(leftHandOperand, rightHandOperand, memoizeMap) {
function memoizeSet (line 9727) | function memoizeSet(leftHandOperand, rightHandOperand, memoizeMap, resul...
function deepEqual (line 9761) | function deepEqual(leftHandOperand, rightHandOperand, options) {
function simpleEqual (line 9782) | function simpleEqual(leftHandOperand, rightHandOperand) {
function extensiveDeepEqual (line 9818) | function extensiveDeepEqual(leftHandOperand, rightHandOperand, options) {
function extensiveDeepEqualByType (line 9864) | function extensiveDeepEqualByType(leftHandOperand, rightHandOperand, lef...
function regexpEqual (line 9916) | function regexpEqual(leftHandOperand, rightHandOperand) {
function entriesEqual (line 9929) | function entriesEqual(leftHandOperand, rightHandOperand, options) {
function iterableEqual (line 9957) | function iterableEqual(leftHandOperand, rightHandOperand, options) {
function generatorEqual (line 9983) | function generatorEqual(leftHandOperand, rightHandOperand, options) {
function hasIteratorFunction (line 9993) | function hasIteratorFunction(target) {
function getIteratorEntries (line 10007) | function getIteratorEntries(target) {
function getGeneratorEntries (line 10024) | function getGeneratorEntries(generator) {
function getEnumerableKeys (line 10040) | function getEnumerableKeys(target) {
function keysEqual (line 10058) | function keysEqual(leftHandOperand, rightHandOperand, keys, options) {
function objectEqual (line 10081) | function objectEqual(leftHandOperand, rightHandOperand, options) {
function isPrimitive (line 10120) | function isPrimitive(value) {
function getFuncName (line 10148) | function getFuncName(aFunc) {
function hasProperty (line 10217) | function hasProperty(obj, name) {
function parsePath (line 10245) | function parsePath(path) {
function internalGetPathValue (line 10277) | function internalGetPathValue(obj, parsed, pathDepth) {
function internalSetPathValue (line 10314) | function internalSetPathValue(obj, val, parsed) {
function getPathInfo (line 10368) | function getPathInfo(obj, path) {
function getPathValue (line 10412) | function getPathValue(obj, path) {
function setPathValue (line 10450) | function setPathValue(obj, path, val) {
function typeDetect (line 10508) | function typeDetect(obj) {
FILE: src/tests/lib/mocha/mocha.js
function r (line 1) | function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==...
function timeslice (line 79) | function timeslice() {
function isPermitted (line 267) | function isPermitted() {
function canNotify (line 303) | function canNotify(value) {
function display (line 318) | function display(runner) {
function notPermitted (line 363) | function notPermitted(err) {
function Progress (line 380) | function Progress() {
function Context (line 522) | function Context() {}
function createNoFilesMatchPatternError (line 626) | function createNoFilesMatchPatternError(message, pattern) {
function createInvalidReporterError (line 641) | function createInvalidReporterError(message, reporter) {
function createInvalidInterfaceError (line 656) | function createInvalidInterfaceError(message, ui) {
function createUnsupportedError (line 670) | function createUnsupportedError(message) {
function createMissingArgumentError (line 685) | function createMissingArgumentError(message, argument, expected) {
function createInvalidArgumentTypeError (line 698) | function createInvalidArgumentTypeError(message, argument, expected) {
function createInvalidArgumentValueError (line 717) | function createInvalidArgumentValueError(message, argument, value, reaso...
function createInvalidExceptionError (line 733) | function createInvalidExceptionError(message, value) {
function Hook (line 772) | function Hook(title, fn) {
function shouldBeTested (line 943) | function shouldBeTested(suite) {
function visit (line 1140) | function visit(obj, file) {
function Mocha (line 1488) | function Mocha(options) {
function done (line 2323) | function done(failures) {
function Pending (line 2359) | function Pending(message) {
function stringifyDiffObjs (line 2530) | function stringifyDiffObjs(err) {
function Base (line 2661) | function Base(runner, options) {
function pad (line 2743) | function pad(str, len) {
function inlineDiff (line 2756) | function inlineDiff(actual, expected) {
function unifiedDiff (line 2793) | function unifiedDiff(actual, expected) {
function errorDiff (line 2836) | function errorDiff(actual, expected) {
function colorLines (line 2859) | function colorLines(name, str) {
function sameType (line 2881) | function sameType(a, b) {
function Doc (line 2923) | function Doc(runner, options) {
function Dot (line 3016) | function Dot(runner, options) {
function HTML (line 3126) | function HTML(runner, options) {
function makeUrl (line 3322) | function makeUrl(s) {
function error (line 3379) | function error(msg) {
function fragment (line 3388) | function fragment(html) {
function hideSuitesWithout (line 3412) | function hideSuitesWithout(classname) {
function unhide (line 3425) | function unhide() {
function text (line 3438) | function text(el, contents) {
function on (line 3449) | function on(el, event, fn) {
function JSONStream (line 3514) | function JSONStream(runner, options) {
function writeEvent (line 3551) | function writeEvent(event) {
function clean (line 3563) | function clean(test) {
function JSONReporter (line 3609) | function JSONReporter(runner, options) {
function clean (line 3657) | function clean(test) {
function cleanCycles (line 3679) | function cleanCycles(obj) {
function errorJSON (line 3703) | function errorJSON(err) {
function Landing (line 3769) | function Landing(runner, options) {
function List (line 3864) | function List(runner, options) {
function Markdown (line 3948) | function Markdown(runner, options) {
function Min (line 4059) | function Min(runner, options) {
function NyanCat (line 4115) | function NyanCat(runner, options) {
function draw (line 4185) | function draw(type, n) {
function write (line 4353) | function write(string) {
function Progress (line 4401) | function Progress(runner, options) {
function Spec (line 4505) | function Spec(runner, options) {
function TAP (line 4607) | function TAP(runner, options) {
function title (line 4661) | function title(test) {
function println (line 4672) | function println(format, varArgs) {
function createProducer (line 4686) | function createProducer(tapVersion) {
function TAPProducer (line 4712) | function TAPProducer() {}
function TAP12Producer (line 4791) | function TAP12Producer() {
function TAP13Producer (line 4824) | function TAP13Producer() {
function XUnit (line 4914) | function XUnit(runner, options) {
function tag (line 5066) | function tag(name, attrs, close, content) {
function Runnable (line 5118) | function Runnable(title, fn) {
function multiple (line 5367) | function multiple(err) {
function done (line 5382) | function done(err) {
function callFn (line 5457) | function callFn(fn) {
function callFnAsync (line 5485) | function callFnAsync(fn) {
function Runner (line 5698) | function Runner(suite, delay) {
function next (line 5930) | function next(i) {
function next (line 6016) | function next(suite) {
function hookErr (line 6130) | function hookErr(_, errSuite, after) {
function next (line 6156) | function next(err, errSuite) {
function alwaysFalse (line 6283) | function alwaysFalse() {
function next (line 6307) | function next(errSuite) {
function done (line 6341) | function done(errSuite) {
function uncaught (line 6462) | function uncaught(err) {
function start (line 6466) | function start() {
function filterLeaks (line 6538) | function filterLeaks(ok, globals) {
function isError (line 6581) | function isError(err) {
function thrown2Error (line 6593) | function thrown2Error(err) {
function createStatsCollector (line 6650) | function createStatsCollector(runner) {
function Suite (line 6743) | function Suite(title, parentContext, isRoot) {
function cleanArrReferences (line 7220) | function cleanArrReferences(arr) {
function Test (line 7358) | function Test(title, fn) {
function highlight (line 7534) | function highlight(js) {
function emptyRepresentation (line 7579) | function emptyRepresentation(value, typeHint) {
function jsonStringify (line 7689) | function jsonStringify(object, spaces, depth) {
function withStack (line 7791) | function withStack(value, fn) {
function hasMatchingExtname (line 7862) | function hasMatchingExtname(pathname, exts) {
function isHiddenOnUnix (line 7884) | function isHiddenOnUnix(pathname) {
function emitWarning (line 7992) | function emitWarning(msg, type) {
function isMochaInternal (line 8055) | function isMochaInternal(line) {
function isNodeInternal (line 8063) | function isNodeInternal(line) {
function getLens (line 8254) | function getLens (b64) {
function byteLength (line 8274) | function byteLength (b64) {
function _byteLength (line 8281) | function _byteLength (b64, validLen, placeHoldersLen) {
function toByteArray (line 8285) | function toByteArray (b64) {
function tripletToBase64 (line 8330) | function tripletToBase64 (num) {
function encodeChunk (line 8337) | function encodeChunk (uint8, start, end) {
function fromByteArray (line 8350) | function fromByteArray (uint8) {
function BrowserStdout (line 8397) | function BrowserStdout(opts) {
function typedArraySupport (line 8464) | function typedArraySupport () {
function createBuffer (line 8491) | function createBuffer (length) {
function Buffer (line 8511) | function Buffer (arg, encodingOrOffset, length) {
function from (line 8537) | function from (value, encodingOrOffset, length) {
function assertSize (line 8602) | function assertSize (size) {
function alloc (line 8610) | function alloc (size, fill, encoding) {
function allocUnsafe (line 8634) | function allocUnsafe (size) {
function fromString (line 8652) | function fromString (string, encoding) {
function fromArrayLike (line 8676) | function fromArrayLike (array) {
function fromArrayBuffer (line 8685) | function fromArrayBuffer (array, byteOffset, length) {
function fromObject (line 8708) | function fromObject (obj) {
function checked (line 8733) | function checked (length) {
function SlowBuffer (line 8743) | function SlowBuffer (length) {
function byteLength (line 8834) | function byteLength (string, encoding) {
function slowToString (line 8883) | function slowToString (encoding, start, end) {
function swap (line 8961) | function swap (b, n, m) {
function bidirectionalIndexOf (line 9101) | function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
function arrayIndexOf (line 9157) | function arrayIndexOf (arr, val, byteOffset, encoding, dir) {
function hexWrite (line 9225) | function hexWrite (buf, string, offset, length) {
function utf8Write (line 9250) | function utf8Write (buf, string, offset, length) {
function asciiWrite (line 9254) | function asciiWrite (buf, string, offset, length) {
function latin1Write (line 9258) | function latin1Write (buf, string, offset, length) {
function base64Write (line 9262) | function base64Write (buf, string, offset, length) {
function ucs2Write (line 9266) | function ucs2Write (buf, string, offset, length) {
function base64Slice (line 9348) | function base64Slice (buf, start, end) {
function utf8Slice (line 9356) | function utf8Slice (buf, start, end) {
function decodeCodePointsArray (line 9434) | function decodeCodePointsArray (codePoints) {
function asciiSlice (line 9452) | function asciiSlice (buf, start, end) {
function latin1Slice (line 9462) | function latin1Slice (buf, start, end) {
function hexSlice (line 9472) | function hexSlice (buf, start, end) {
function utf16leSlice (line 9485) | function utf16leSlice (buf, start, end) {
function checkOffset (line 9524) | function checkOffset (offset, ext, length) {
function checkInt (line 9699) | function checkInt (buf, value, offset, ext, max, min) {
function checkIEEE754 (line 9887) | function checkIEEE754 (buf, value, offset, ext, max, min) {
function writeFloat (line 9892) | function writeFloat (buf, value, offset, littleEndian, noAssert) {
function writeDouble (line 9910) | function writeDouble (buf, value, offset, littleEndian, noAssert) {
function base64clean (line 10049) | function base64clean (str) {
function toHex (line 10063) | function toHex (n) {
function utf8ToBytes (line 10068) | function utf8ToBytes (string, units) {
function asciiToBytes (line 10148) | function asciiToBytes (str) {
function utf16leToBytes (line 10157) | function utf16leToBytes (str, units) {
function base64ToBytes (line 10173) | function base64ToBytes (str) {
function blitBuffer (line 10177) | function blitBuffer (src, dst, offset, length) {
function isInstance (line 10188) | function isInstance (obj, type) {
function numberIsNaN (line 10193) | function numberIsNaN (obj) {
function isArray (line 10225) | function isArray(arg) {
function isBoolean (line 10233) | function isBoolean(arg) {
function isNull (line 10238) | function isNull(arg) {
function isNullOrUndefined (line 10243) | function isNullOrUndefined(arg) {
function isNumber (line 10248) | function isNumber(arg) {
function isString (line 10253) | function isString(arg) {
function isSymbol (line 10258) | function isSymbol(arg) {
function isUndefined (line 10263) | function isUndefined(arg) {
function isRegExp (line 10268) | function isRegExp(re) {
function isObject (line 10273) | function isObject(arg) {
function isDate (line 10278) | function isDate(d) {
function isError (line 10283) | function isError(e) {
function isFunction (line 10288) | function isFunction(arg) {
function isPrimitive (line 10293) | function isPrimitive(arg) {
function objectToString (line 10305) | function objectToString(o) {
function _typeof (line 10314) | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbo...
function useColors (line 10341) | function useColors() {
function formatArgs (line 10369) | function formatArgs(args) {
function log (line 10406) | function log() {
function save (line 10421) | function save(namespaces) {
function load (line 10440) | function load() {
function localstorage (line 10468) | function localstorage() {
function setup (line 10501) | function setup(env) {
function __webpack_require__ (line 10858) | function __webpack_require__(moduleId) {
function _interopRequireDefault (line 10934) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function Diff (line 10983) | function Diff() {}
function done (line 10998) | function done(value) {
function execEditLength (line 11030) | function execEditLength() {
function buildValues (line 11157) | function buildValues(diff, components, newString, oldString, useLongestT...
function clonePath (line 11210) | function clonePath(path) {
function _interopRequireDefault (line 11230) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function diffChars (line 11233) | function diffChars(oldStr, newStr, options) {
function _interopRequireDefault (line 11256) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function diffWords (line 11304) | function diffWords(oldStr, newStr, options) {
function diffWordsWithSpace (line 11309) | function diffWordsWithSpace(oldStr, newStr, options) {
function generateOptions (line 11323) | function generateOptions(options, defaults) {
function _interopRequireDefault (line 11356) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function diffLines (line 11385) | function diffLines(oldStr, newStr, callback) {
function diffTrimmedLines (line 11388) | function diffTrimmedLines(oldStr, newStr, callback) {
function _interopRequireDefault (line 11409) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function diffSentences (line 11416) | function diffSentences(oldStr, newStr, callback) {
function _interopRequireDefault (line 11436) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function diffCss (line 11443) | function diffCss(oldStr, newStr, callback) {
function _interopRequireDefault (line 11469) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function diffJson (line 11496) | function diffJson(oldObj, newObj, options) {
function canonicalize (line 11502) | function canonicalize(obj, stack, replacementStack, replacer, key) {
function _interopRequireDefault (line 11577) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function diffArrays (line 11587) | function diffArrays(oldArr, newArr, callback) {
function _interopRequireDefault (line 11609) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function applyPatch (line 11611) | function applyPatch(source, uniDiff) {
function applyPatches (line 11747) | function applyPatches(uniDiff, options) {
function parsePatch (line 11787) | function parsePatch(uniDiff) {
function _toConsumableArray (line 12001) | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i ...
function calcLineCount (line 12003) | function calcLineCount(hunk) {
function merge (line 12021) | function merge(mine, theirs, base) {
function loadPatch (line 12097) | function loadPatch(param, base) {
function fileNameChanged (line 12114) | function fileNameChanged(patch) {
function selectField (line 12118) | function selectField(index, mine, theirs) {
function hunkBefore (line 12127) | function hunkBefore(test, check) {
function cloneHunk (line 12131) | function cloneHunk(hunk, offset) {
function mergeLines (line 12139) | function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) {
function mutualChange (line 12191) | function mutualChange(hunk, mine, their) {
function removal (line 12218) | function removal(hunk, mine, their, swap) {
function conflict (line 12230) | function conflict(hunk, mine, their) {
function insertLeading (line 12239) | function insertLeading(hunk, insert, their) {
function insertTrailing (line 12246) | function insertTrailing(hunk, insert) {
function collectChange (line 12253) | function collectChange(state) {
function collectContext (line 12274) | function collectContext(state, matchChanges) {
function allRemoves (line 12331) | function allRemoves(changes) {
function skipRemoveSuperset (line 12336) | function skipRemoveSuperset(state, removeChanges, delta) {
function calcOldNewLineCount (line 12348) | function calcOldNewLineCount(lines) {
function _toConsumableArray (line 12400) | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i ...
function structuredPatch (line 12402) | function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHe...
function createTwoFilesPatch (line 12515) | function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, o...
function createPatch (line 12535) | function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, opt...
function arrayEqual (line 12550) | function arrayEqual(a, b) {
function arrayStartsWith (line 12558) | function arrayStartsWith(array, start) {
function convertChangesToDMP (line 12583) | function convertChangesToDMP(changes) {
function convertChangesToXML (line 12612) | function convertChangesToXML(changes) {
function escapeHTML (line 12633) | function escapeHTML(s) {
function EventEmitter (line 12688) | function EventEmitter() {
function $getMaxListeners (line 12741) | function $getMaxListeners(that) {
function emitNone (line 12756) | function emitNone(handler, isFn, self) {
function emitOne (line 12766) | function emitOne(handler, isFn, self, arg1) {
function emitTwo (line 12776) | function emitTwo(handler, isFn, self, arg1, arg2) {
function emitThree (line 12786) | function emitThree(handler, isFn, self, arg1, arg2, arg3) {
function emitMany (line 12797) | function emitMany(handler, isFn, self, args) {
function _addListener (line 12865) | function _addListener(target, type, listener, prepend) {
function onceWrapper (line 12943) | function onceWrapper() {
function _onceWrap (line 12966) | function _onceWrap(target, type, listener) {
function _listeners (line 13093) | function _listeners(target, type, unwrap) {
function listenerCount (line 13126) | function listenerCount(type) {
function spliceOne (line 13147) | function spliceOne(list, index) {
function arrayClone (line 13153) | function arrayClone(arr, n) {
function unwrapListeners (line 13160) | function unwrapListeners(arr) {
function objectCreatePolyfill (line 13168) | function objectCreatePolyfill(proto) {
function objectKeysPolyfill (line 13173) | function objectKeysPolyfill(obj) {
function functionBindPolyfill (line 13180) | function functionBindPolyfill(context) {
function isBuffer (line 13764) | function isBuffer (obj) {
function isSlowBuffer (line 13769) | function isSlowBuffer (obj) {
function mkdirP (line 13787) | function mkdirP (p, opts, f, made) {
function parse (line 13929) | function parse(str) {
function fmtShort (line 13994) | function fmtShort(ms) {
function fmtLong (line 14019) | function fmtLong(ms) {
function plural (line 14040) | function plural(ms, msAbs, n, name) {
function nextTick (line 14363) | function nextTick(fn, arg1, arg2, arg3) {
function defaultSetTimout (line 14411) | function defaultSetTimout() {
function defaultClearTimeout (line 14414) | function defaultClearTimeout () {
function runTimeout (line 14437) | function runTimeout(fun) {
function runClearTimeout (line 14462) | function runClearTimeout(marker) {
function cleanUpNextTick (line 14494) | function cleanUpNextTick() {
function drainQueue (line 14509) | function drainQueue() {
function Item (line 14547) | function Item(fun, array) {
function noop (line 14561) | function noop() {}
function Duplex (line 14652) | function Duplex(options) {
function onend (line 14679) | function onend() {
function onEndNT (line 14689) | function onEndNT(self) {
function PassThrough (line 14759) | function PassThrough(options) {
function _uint8ArrayToBuffer (line 14826) | function _uint8ArrayToBuffer(chunk) {
function _isUint8Array (line 14829) | function _isUint8Array(obj) {
function prependListener (line 14858) | function prependListener(emitter, event, fn) {
function ReadableState (line 14870) | function ReadableState(options, stream) {
function Readable (line 14947) | function Readable(options) {
function readableAddChunk (line 15022) | function readableAddChunk(stream, chunk, encoding, addToFront, skipChunk...
function addChunk (line 15058) | function addChunk(stream, state, chunk, addToFront) {
function chunkInvalid (line 15072) | function chunkInvalid(state, chunk) {
function needMoreData (line 15087) | function needMoreData(state) {
function computeNewHighWaterMark (line 15105) | function computeNewHighWaterMark(n) {
function howMuchToRead (line 15124) | function howMuchToRead(n, state) {
function onEofChunk (line 15243) | function onEofChunk(stream, state) {
function emitReadable (line 15261) | function emitReadable(stream) {
function emitReadable_ (line 15271) | function emitReadable_(stream) {
function maybeReadMore (line 15283) | function maybeReadMore(stream, state) {
function maybeReadMore_ (line 15290) | function maybeReadMore_(stream, state) {
function onunpipe (line 15334) | function onunpipe(readable, unpipeInfo) {
function onend (line 15344) | function onend() {
function cleanup (line 15357) | function cleanup() {
function ondata (line 15385) | function ondata(chunk) {
function onerror (line 15405) | function onerror(er) {
function onclose (line 15416) | function onclose() {
function onfinish (line 15421) | function onfinish() {
function unpipe (line 15428) | function unpipe() {
function pipeOnDrain (line 15445) | function pipeOnDrain(src) {
function nReadingNextTick (line 15532) | function nReadingNextTick(self) {
function resume (line 15549) | function resume(stream, state) {
function resume_ (line 15556) | function resume_(stream, state) {
function flow (line 15579) | function flow(stream) {
function fromList (line 15665) | function fromList(n, state) {
function fromListPartial (line 15685) | function fromListPartial(n, list, hasStrings) {
function copyFromBufferString (line 15705) | function copyFromBufferString(n, list) {
function copyFromBuffer (line 15734) | function copyFromBuffer(n, list) {
function endReadable (line 15761) | function endReadable(stream) {
function endReadableNT (line 15774) | function endReadableNT(state, stream) {
function indexOf (line 15783) | function indexOf(xs, x) {
function afterTransform (line 15867) | function afterTransform(er, data) {
function Transform (line 15892) | function Transform(options) {
function prefinish (line 15924) | function prefinish() {
function done (line 15991) | function done(stream, er, data) {
function WriteReq (line 16042) | function WriteReq(chunk, encoding, cb) {
function CorkedRequest (line 16051) | function CorkedRequest(state) {
function _uint8ArrayToBuffer (line 16091) | function _uint8ArrayToBuffer(chunk) {
function _isUint8Array (line 16094) | function _isUint8Array(obj) {
function nop (line 16104) | function nop() {}
function WritableState (line 16106) | function WritableState(options, stream) {
function Writable (line 16256) | function Writable(options) {
function writeAfterEnd (line 16293) | function writeAfterEnd(stream, cb) {
function validChunk (line 16303) | function validChunk(stream, state, chunk, cb) {
function decodeChunk (line 16370) | function decodeChunk(state, chunk, encoding) {
function writeOrBuffer (line 16390) | function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) {
function doWrite (line 16429) | function doWrite(stream, state, writev, len, chunk, encoding, cb) {
function onwriteError (line 16438) | function onwriteError(stream, state, sync, er, cb) {
function onwriteStateUpdate (line 16462) | function onwriteStateUpdate(state) {
function onwrite (line 16469) | function onwrite(stream, er) {
function afterWrite (line 16494) | function afterWrite(stream, state, finished, cb) {
function onwriteDrain (line 16504) | function onwriteDrain(stream, state) {
function clearBuffer (line 16512) | function clearBuffer(stream, state) {
function needFinish (line 16603) | function needFinish(state) {
function callFinal (line 16606) | function callFinal(stream, state) {
function prefinish (line 16617) | function prefinish(stream, state) {
function finishMaybe (line 16630) | function finishMaybe(stream, state) {
function endWritable (line 16642) | function endWritable(stream, state, cb) {
function onCorkedFinish (line 16652) | function onCorkedFinish(corkReq, state, err) {
function _classCallCheck (line 16698) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function copyBuffer (line 16703) | function copyBuffer(src, target, offset) {
function BufferList (line 16708) | function BufferList() {
function destroy (line 16784) | function destroy(err, cb) {
function undestroy (line 16825) | function undestroy() {
function emitErrorNT (line 16842) | function emitErrorNT(self, err) {
function copyProps (line 16877) | function copyProps (src, dst) {
function SafeBuffer (line 16890) | function SafeBuffer (arg, encodingOrOffset, length) {
function Stream (line 16977) | function Stream() {
function ondata (line 16984) | function ondata(chunk) {
function ondrain (line 16994) | function ondrain() {
function onend (line 17010) | function onend() {
function onclose (line 17018) | function onclose() {
function onerror (line 17026) | function onerror(er) {
function cleanup (line 17037) | function cleanup() {
function _normalizeEncoding (line 17103) | function _normalizeEncoding(enc) {
function normalizeEncoding (line 17133) | function normalizeEncoding(enc) {
function StringDecoder (line 17143) | function StringDecoder(encoding) {
function utf8CheckByte (line 17204) | function utf8CheckByte(byte) {
function utf8CheckIncomplete (line 17212) | function utf8CheckIncomplete(self, buf, i) {
function utf8CheckExtraBytes (line 17245) | function utf8CheckExtraBytes(self, buf, p) {
function utf8FillLast (line 17265) | function utf8FillLast(buf) {
function utf8Text (line 17280) | function utf8Text(buf, i) {
function utf8End (line 17291) | function utf8End(buf) {
function utf16Text (line 17301) | function utf16Text(buf, i) {
function utf16End (line 17324) | function utf16End(buf) {
function base64Text (line 17333) | function base64Text(buf, i) {
function base64End (line 17347) | function base64End(buf) {
function simpleWrite (line 17354) | function simpleWrite(buf) {
function simpleEnd (line 17358) | function simpleEnd(buf) {
function Timeout (line 17380) | function Timeout(id, clearFn) {
function deprecate (line 17467) | function deprecate (fn, msg) {
function config (line 17498) | function config (name) {
function deprecated (line 17597) | function deprecated() {
function inspect (line 17644) | function inspect(obj, opts) {
function stylizeWithColor (line 17702) | function stylizeWithColor(str, styleType) {
function stylizeNoColor (line 17714) | function stylizeNoColor(str, styleType) {
function arrayToHash (line 17719) | function arrayToHash(array) {
function formatValue (line 17730) | function formatValue(ctx, value, recurseTimes) {
function formatPrimitive (line 17843) | function formatPrimitive(ctx, value) {
function formatError (line 17862) | function formatError(value) {
function formatArray (line 17867) | function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
function formatProperty (line 17887) | function formatProperty(ctx, value, recurseTimes, visibleKeys, key, arra...
function reduceToSingleString (line 17946) | function reduceToSingleString(output, base, braces) {
function isArray (line 17969) | function isArray(ar) {
function isBoolean (line 17974) | function isBoolean(arg) {
function isNull (line 17979) | function isNull(arg) {
function isNullOrUndefined (line 17984) | function isNullOrUndefined(arg) {
function isNumber (line 17989) | function isNumber(arg) {
function isString (line 17994) | function isString(arg) {
function isSymbol (line 17999) | function isSymbol(arg) {
function isUndefined (line 18004) | function isUndefined(arg) {
function isRegExp (line 18009) | function isRegExp(re) {
function isObject (line 18014) | function isObject(arg) {
function isDate (line 18019) | function isDate(d) {
function isError (line 18024) | function isError(e) {
function isFunction (line 18030) | function isFunction(arg) {
function isPrimitive (line 18035) | function isPrimitive(arg) {
function objectToString (line 18047) | function objectToString(o) {
function pad (line 18052) | function pad(n) {
function timestamp (line 18061) | function timestamp() {
function hasOwnProperty (line 18103) | function hasOwnProperty(obj, prop) {
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,077K chars).
[
{
"path": ".gitignore",
"chars": 44,
"preview": "/build/node_modules\n/build/web-ext-artifacts"
},
{
"path": "CONTRIBUTORS",
"chars": 452,
"preview": "Extension code by:\n- Denys Honsiorovskyi (https://github.com/honsiorovskyi)\n- attero (https://github.com/apfelchips)\n\nEx"
},
{
"path": "LICENSE",
"chars": 16726,
"preview": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\""
},
{
"path": "README.md",
"chars": 6580,
"preview": "# Open external links in a container\n\n<img src=\"src/icons/extension-96.png\">\n\n_**Important:** this code corresponds to a"
},
{
"path": "bin/launcher.sh",
"chars": 3820,
"preview": "#!/bin/bash\n\n# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the M"
},
{
"path": "build/.eslintrc.json",
"chars": 1274,
"preview": "{\n \"env\": {\n \"browser\": true,\n \"es6\": true,\n \"mocha\": true\n },\n \"extends\": \"eslint:recomme"
},
{
"path": "build/package.json",
"chars": 630,
"preview": "{\n \"name\": \"open-url-in-container\",\n \"author\": {\n \"name\": \"Denys Honsiorovskyi\",\n \"url\": \"https://gi"
},
{
"path": "src/js/config.js",
"chars": 1313,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/containers.js",
"chars": 1434,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/opener/opener.js",
"chars": 2192,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/opener/parser.js",
"chars": 1615,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/opener/validator.js",
"chars": 3087,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/params.js",
"chars": 2191,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/popup/bookmark.js",
"chars": 3300,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/popup/containers.js",
"chars": 3736,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/popup/dom.js",
"chars": 537,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/popup/folders.js",
"chars": 1646,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/popup/popup.js",
"chars": 3197,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/popup/signature.js",
"chars": 380,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/popup/state.js",
"chars": 668,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/popup/terminal.js",
"chars": 641,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/popup/url.js",
"chars": 365,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/security/hex.js",
"chars": 1335,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/security/keys.js",
"chars": 831,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/security/signature.js",
"chars": 897,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/js/tabs.js",
"chars": 1318,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/manifest.json",
"chars": 1105,
"preview": "{\n \"manifest_version\": 2,\n \"name\": \"Open links in containers\",\n \"description\": \"This extension enables support "
},
{
"path": "src/opener.html",
"chars": 3733,
"preview": "<!-- This Source Code Form is subject to the terms of the Mozilla Public\n -- License, v. 2.0. If a copy of the MPL was "
},
{
"path": "src/popup.html",
"chars": 10477,
"preview": "<!-- This Source Code Form is subject to the terms of the Mozilla Public\n -- License, v. 2.0. If a copy of the MPL was "
},
{
"path": "src/tests/lib/chai/chai.js",
"chars": 345751,
"preview": "(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"func"
},
{
"path": "src/tests/lib/mocha/mocha.css",
"chars": 5590,
"preview": "@charset \"utf-8\";\n\nbody {\n margin:0;\n}\n\n#mocha {\n font: 20px/1.5 \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n mar"
},
{
"path": "src/tests/lib/mocha/mocha.js",
"chars": 588837,
"preview": "(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c=\"function\"==typeof require&&require;if(!f&&c)ret"
},
{
"path": "src/tests/opener/parsers.test.js",
"chars": 5016,
"preview": "import { parseOpenerParams } from '../../js/opener/parser.js'\n\ndescribe('opener/parser.js', () => {\n const hashString"
},
{
"path": "src/tests/opener/validator.test.js",
"chars": 8753,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
},
{
"path": "src/tests/security/hex.test.js",
"chars": 1438,
"preview": "import { hex2array, array2hex } from '../../js/security/hex.js'\n\ndescribe('security/hex.js', () => {\n describe('secur"
},
{
"path": "src/tests/testdata/example-links.html",
"chars": 930,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <link rel=\"stylesheet\" href=\"ext+container:name=Demo&url=https://example.org/style.cs"
},
{
"path": "src/tests/unit.html",
"chars": 881,
"preview": "<!-- This Source Code Form is subject to the terms of the Mozilla Public\n -- License, v. 2.0. If a copy of the MPL was "
},
{
"path": "src/tests/unit.test.js",
"chars": 344,
"preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
}
]
About this extraction
This page contains the full source code of the honsiorovskyi/open-url-in-container GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (1008.9 KB), approximately 292.1k tokens, and a symbol index with 579 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.