├── rollup.config.js ├── test ├── mocks │ ├── jsapi3x.js │ └── jsapi4x.js └── helpers.ts ├── .github ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ └── main.yml └── ISSUE_TEMPLATE.md ├── profiles ├── base.config.js └── prod.config.js ├── src ├── utils │ ├── index.ts │ ├── url.test.ts │ ├── url.ts │ ├── css.ts │ └── css.test.ts ├── esri-loader.ts ├── modules.ts ├── modules.test.ts ├── script.ts └── script.test.ts ├── tslint.json ├── tsconfig.json ├── scripts └── release.sh ├── .npmignore ├── .gitignore ├── package.json ├── karma.conf.js ├── archived-examples.md ├── LICENSE ├── CHANGELOG.md └── README.md /rollup.config.js: -------------------------------------------------------------------------------- 1 | import base from './profiles/base.config.js'; 2 | 3 | export default base; 4 | -------------------------------------------------------------------------------- /test/mocks/jsapi3x.js: -------------------------------------------------------------------------------- 1 | console.log('3.x'); 2 | // NOTE: this is defined in /src/script.test.ts 3 | stubRequire(); 4 | -------------------------------------------------------------------------------- /test/mocks/jsapi4x.js: -------------------------------------------------------------------------------- 1 | console.log('4.x'); 2 | // NOTE: this is defined in /src/script.test.ts 3 | stubRequire(); 4 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). 2 | -------------------------------------------------------------------------------- /profiles/base.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: 'dist/esm/esri-loader.js', 3 | format: 'umd', 4 | moduleName: 'esriLoader', 5 | exports: 'named', 6 | dest: 'dist/umd/esri-loader.js', 7 | sourceMap: true 8 | }; 9 | -------------------------------------------------------------------------------- /profiles/prod.config.js: -------------------------------------------------------------------------------- 1 | import uglify from 'rollup-plugin-uglify'; 2 | import base from './base.config.js'; 3 | 4 | export default Object.assign({}, base, { 5 | dest: 'dist/umd/esri-loader.min.js', 6 | plugins: [ 7 | uglify() 8 | ] 9 | }); 10 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2022 Environmental Systems Research Institute, Inc. 2 | * Apache-2.0 */ 3 | 4 | const isBrowser = typeof window !== 'undefined'; 5 | 6 | // allow consuming libraries to provide their own Promise implementations 7 | export default { 8 | Promise: isBrowser ? window['Promise'] : undefined 9 | }; 10 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:latest", 3 | "rules": { 4 | "quotemark": ["single"], 5 | "no-string-literal": false, 6 | "trailing-comma": [ 7 | true, 8 | { 9 | "multiline": "never", 10 | "singleline": "never" 11 | } 12 | ], 13 | "object-literal-sort-keys": false, 14 | "variable-name": [true, "check-format", "allow-leading-underscore"], 15 | "no-console": false 16 | } 17 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - Describe the proposed changes: 2 | 3 | - Is there an example or test page to demonstrate any new or changed features? 4 | - Does your PR include appropriate tests for source code alterations? 5 | - If you're adding or changing a public API, did you update the docs Usage sections of the README? If not, please provide a code snippet that demonstrates how to consume the new or updated API(s). 6 | 7 | - Provide a reference to any related issue. 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "target": "es5", 5 | "module": "es2015", 6 | "lib": ["dom", "es2015"], 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "removeComments": false, 10 | "noImplicitAny": false, 11 | "outDir": "dist/esm", 12 | "types": [] 13 | }, 14 | "files": [ 15 | "src/esri-loader.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | CHANGE="$1" 4 | 5 | # update CHANGELOG 6 | carriage-return --level "$CHANGE" 7 | 8 | # bump version 9 | # NOTE: force is needed so that the preversion script can run 10 | # and preversion creates production build and runs tests 11 | npm version "$CHANGE" --force 12 | 13 | # push version commit and tags to github 14 | git push --follow-tags 15 | 16 | # publish to npm 17 | # TODO: uncomment this once this script is locked and loaded 18 | # npm publish 19 | -------------------------------------------------------------------------------- /test/helpers.ts: -------------------------------------------------------------------------------- 1 | export const jaspi3xUrl = 'base/test/mocks/jsapi3x.js'; 2 | 3 | // test helper functions 4 | 5 | // stub global require function 6 | export function stubRequire() { 7 | window.require = function(moduleNames, callback) { 8 | if (callback) { 9 | // call the callback w/ the module names that were passed in 10 | callback.apply(this, moduleNames); 11 | } 12 | }; 13 | window.require.on = (name, callback) => { 14 | return { 15 | /* tslint:disable no-empty */ 16 | remove() {} 17 | /* tslint:enable no-empty */ 18 | }; 19 | }; 20 | } 21 | 22 | // remove previously stubbed require function 23 | export function removeRequire() { 24 | delete window.require; 25 | } 26 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | # build output 41 | dist 42 | -------------------------------------------------------------------------------- /src/esri-loader.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022 Esri 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | // re-export the functions that are part of the public API 15 | import utils from './utils/index'; 16 | export { loadModules } from './modules'; 17 | export { getScript, isLoaded, loadScript, setDefaultOptions } from './script'; 18 | export { ILoadScriptOptions } from './script'; 19 | export { loadCss } from './utils/css'; 20 | export { utils }; 21 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # install dependencies, run build and tests 2 | 3 | name: Node.js CI 4 | 5 | on: 6 | push: 7 | branches: [ master ] 8 | pull_request: 9 | branches: [ master ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | node-version: [14.x, 16.x] 22 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | cache: 'yarn' 31 | - name: Install dependencies 32 | run: yarn 33 | - name: Install Firefox 34 | uses: browser-actions/setup-firefox@latest 35 | - name: Run tests 36 | run: env FIREFOX_BIN=`which firefox` yarn ci 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected behavior 2 | 3 | - Describe what you expected or wanted to happen. 4 | - What you are trying to achieve? 5 | - Describe your environment/framework and be specific with version numbers (e.g. React 16.2, react-router 4.2, redux 3.7, node 8.3). 6 | 7 | ### Actual behavior 8 | 9 | - Describe what occurs in your code. 10 | - Specifically, what seems to work differently than you intended? 11 | - Provide any error messages you see in the console. 12 | 13 | ### Steps to reproduce the behavior 14 | 15 | **We can only help you if we're able to easily reproduce the behavior you describe above.** 16 | 17 | Please provide: 18 | 19 | 1. Steps to reproduce the behavior. 20 | 2. A link to an app where we can carry out those steps and see the source code. 21 | 22 | To help you reproduce issues in an environment that we can't access (e.g. private repository) follow these helpful tips: 23 | 24 | - If the problem is related to the ArcGIS Maps SDK for JavaScript (i.e. behavior of the map, etc), start here https://github.com/Esri/esri-loader#without-a-module-bundler. 25 | - Otherwise, search for and fork a codesandbox that is similar to your environment (React, etc): https://codesandbox.io/search 26 | - For sandboxes that depend on esri-loader see: https://codesandbox.io/search?refinementList[npm_dependencies.dependency][0]=esri-loader 27 | -------------------------------------------------------------------------------- /src/utils/url.test.ts: -------------------------------------------------------------------------------- 1 | import { getCdnCssUrl, getCdnUrl } from './url'; 2 | 3 | describe ('when getting CDN URLs', () => { 4 | describe('for the script', () => { 5 | describe('with no arguments', () => { 6 | it('should default to latest 4.x URL', () => { 7 | expect(getCdnUrl()).toEqual('https://js.arcgis.com/4.25/'); 8 | }); 9 | }); 10 | describe('with a valid version', () => { 11 | it('should return URL for that version', () => { 12 | expect(getCdnUrl('3.42')).toEqual('https://js.arcgis.com/3.42/'); 13 | }); 14 | }); 15 | // TODO: what about an invalid version? should we throw? 16 | }); 17 | describe('for the CSS', () => { 18 | describe('with no arguments', () => { 19 | it('should default to the latest 4.x CSS URL', () => { 20 | expect(getCdnCssUrl()).toEqual('https://js.arcgis.com/4.25/esri/themes/light/main.css'); 21 | }); 22 | }); 23 | describe('for 3.x version >= 3.11', () => { 24 | it('should return the CSS URL for that version', () => { 25 | expect(getCdnCssUrl('3.42')).toEqual('https://js.arcgis.com/3.42/esri/css/esri.css'); 26 | }); 27 | }); 28 | describe('for version < 3.11', () => { 29 | it('should return the CSS URL for that version', () => { 30 | expect(getCdnCssUrl('3.10')).toEqual('https://js.arcgis.com/3.10/js/esri/css/esri.css'); 31 | }); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/utils/url.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2022 Environmental Systems Research Institute, Inc. 2 | * Apache-2.0 */ 3 | 4 | const DEFAULT_VERSION = '4.25'; 5 | const NEXT = 'next'; 6 | 7 | export function parseVersion(version) { 8 | if (version.toLowerCase() === NEXT) { 9 | return NEXT; 10 | } 11 | 12 | const match = version && version.match(/^(\d)\.(\d+)/); 13 | return match && { 14 | major: parseInt(match[1], 10), 15 | minor: parseInt(match[2], 10) 16 | }; 17 | } 18 | 19 | /** 20 | * Get the CDN url for a given version 21 | * 22 | * @param version Ex: '4.25' or '3.42'. Defaults to the latest 4.x version. 23 | */ 24 | export function getCdnUrl(version = DEFAULT_VERSION) { 25 | return `https://js.arcgis.com/${version}/`; 26 | } 27 | 28 | /** 29 | * Get the CDN url for a the CSS for a given version and/or theme 30 | * 31 | * @param version Ex: '4.25', '3.42', or 'next'. Defaults to the latest 4.x version. 32 | */ 33 | export function getCdnCssUrl(version = DEFAULT_VERSION) { 34 | const baseUrl = getCdnUrl(version); 35 | const parsedVersion = parseVersion(version); 36 | if (parsedVersion !== NEXT && parsedVersion.major === 3) { 37 | // NOTE: at 3.11 the CSS moved from the /js folder to the root 38 | const path = parsedVersion.minor <= 10 ? 'js/' : ''; 39 | return `${baseUrl}${path}esri/css/esri.css`; 40 | } else { 41 | // assume 4.x 42 | return `${baseUrl}esri/themes/light/main.css`; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/css.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2022 Environmental Systems Research Institute, Inc. 2 | * Apache-2.0 */ 3 | 4 | import { getCdnCssUrl, parseVersion } from './url'; 5 | 6 | function createStylesheetLink(href: string): HTMLLinkElement { 7 | const link = document.createElement('link'); 8 | link.rel = 'stylesheet'; 9 | link.href = href; 10 | return link; 11 | } 12 | 13 | function insertLink(link: HTMLLinkElement, before?: string) { 14 | if (before) { 15 | // the link should be inserted before a specific node 16 | const beforeNode = document.querySelector(before); 17 | beforeNode.parentNode.insertBefore(link, beforeNode); 18 | } else { 19 | // append the link to then end of the head tag 20 | document.head.appendChild(link); 21 | } 22 | } 23 | 24 | // check if the css url has been injected or added manually 25 | function getCss(url) { 26 | return document.querySelector(`link[href*="${url}"]`) as HTMLLinkElement; 27 | } 28 | 29 | function getCssUrl(urlOrVersion?: string) { 30 | return !urlOrVersion || parseVersion(urlOrVersion) 31 | // if it's a valid version string return the CDN URL 32 | ? getCdnCssUrl(urlOrVersion) 33 | // otherwise assume it's a URL and return that 34 | : urlOrVersion; 35 | } 36 | 37 | // lazy load the CSS needed for the ArcGIS Maps SDK for JavaScript 38 | export function loadCss(urlOrVersion?: string, before?: string) { 39 | const url = getCssUrl(urlOrVersion); 40 | let link = getCss(url); 41 | if (!link) { 42 | // create & load the css link 43 | link = createStylesheetLink(url); 44 | insertLink(link, before); 45 | } 46 | return link; 47 | } 48 | -------------------------------------------------------------------------------- /src/modules.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2022 Environmental Systems Research Institute, Inc. 2 | * Apache-2.0 */ 3 | 4 | import { getScript, ILoadScriptOptions, isLoaded, loadScript } from './script'; 5 | import utils from './utils/index'; 6 | 7 | // wrap Dojo's require() in a promise 8 | function requireModules(modules: string[]): Promise { 9 | return new utils.Promise((resolve, reject) => { 10 | // If something goes wrong loading the esri/dojo scripts, reject with the error. 11 | const errorHandler = window['require'].on('error', reject); 12 | window['require'](modules, (...args) => { 13 | // remove error handler 14 | errorHandler.remove(); 15 | // Resolve with the parameters from dojo require as an array. 16 | resolve(args as T); 17 | }); 18 | }); 19 | } 20 | 21 | // returns a promise that resolves with an array of the required modules 22 | // also will attempt to lazy load the API 23 | // if it has not already been loaded 24 | export function loadModules(modules: string[], loadScriptOptions: ILoadScriptOptions = {}): Promise { 25 | if (!isLoaded()) { 26 | // script is not yet loaded, is it in the process of loading? 27 | const script = getScript(); 28 | const src = script && script.getAttribute('src'); 29 | if (!loadScriptOptions.url && src) { 30 | // script is still loading and user did not specify a URL 31 | // in this case we want to default to the URL that's being loaded 32 | // instead of defaulting to the latest 4.x URL 33 | loadScriptOptions.url = src; 34 | } 35 | // attempt to load the script then load the modules 36 | return loadScript(loadScriptOptions).then(() => requireModules(modules)); 37 | } else { 38 | // script is already loaded, just load the modules 39 | return requireModules(modules); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esri-loader", 3 | "version": "3.7.0", 4 | "description": "A tiny library to help load ArcGIS Maps SDK for JavaScript modules in non-Dojo applications", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/umd/esri-loader.js", 9 | "browser": "dist/umd/esri-loader.js", 10 | "module": "dist/esm/esri-loader.js", 11 | "js:next": "dist/esm/esri-loader.js", 12 | "types": "dist/esm/esri-loader.d.ts", 13 | "scripts": { 14 | "build": "npm run compile && npm run bundle", 15 | "bundle": "rollup -c", 16 | "build:release": "npm run build && npm run bundle -- profiles/prod.config.js", 17 | "compile": "tsc", 18 | "ci": "karma start --single-run=true --browsers FirefoxHeadless", 19 | "clean": "rimraf dist && mkdirp dist", 20 | "lint": "tslint -c tslint.json 'src/esri-loader.ts'", 21 | "prebuild:release": "npm run clean", 22 | "precompile": "npm run lint", 23 | "prepublish": "npm run build:release", 24 | "preversion": "npm run test && git add README.md CHANGELOG.md", 25 | "start": "karma start", 26 | "test": "karma start --single-run=true --browsers Chrome,Firefox" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/Esri/esri-loader.git" 31 | }, 32 | "keywords": [ 33 | "Esri", 34 | "ArcGIS", 35 | "JavaScript", 36 | "module", 37 | "loader", 38 | "Dojo" 39 | ], 40 | "author": "Tom Wayson (https://tomwayson.com)", 41 | "license": "Apache-2.0", 42 | "bugs": { 43 | "url": "https://github.com/Esri/esri-loader/issues" 44 | }, 45 | "homepage": "https://github.com/Esri/esri-loader", 46 | "devDependencies": { 47 | "@types/jasmine": "^2.8.11", 48 | "concurrently": "^3.4.0", 49 | "jasmine-core": "^2.8.0", 50 | "karma": "^6.3.16", 51 | "karma-chrome-launcher": "^2.0.0", 52 | "karma-coverage": "^1.1.2", 53 | "karma-firefox-launcher": "^1.1.0", 54 | "karma-jasmine": "^1.1.0", 55 | "karma-mocha-reporter": "^2.2.3", 56 | "karma-typescript": "^5.5.3", 57 | "mkdirp": "^0.5.1", 58 | "onchange": "^3.2.1", 59 | "rimraf": "^2.6.2", 60 | "rollup": "^0.41.6", 61 | "rollup-plugin-uglify": "^2.0.1", 62 | "tslint": "^5.7.0", 63 | "typescript": "^4.6.3" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed Mar 08 2017 13:05:58 GMT-0800 (PST) 3 | 4 | module.exports = function(config) { 5 | var configuration = { 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine', 'karma-typescript'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | // source code and tests 19 | 'src/**/*.ts', 20 | // test helpers 21 | 'test/**/*.ts', 22 | // serve mock scripts 23 | { pattern: 'test/mocks/*.js', included: false } 24 | ], 25 | 26 | 27 | // list of files to exclude 28 | exclude: [ 29 | ], 30 | 31 | 32 | // preprocess matching files before serving them to the browser 33 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 34 | preprocessors: { 35 | '**/*.ts': 'karma-typescript', 36 | 'src/**/!(*test).ts': 'coverage' 37 | }, 38 | 39 | 40 | // test results reporter to use 41 | // possible values: 'dots', 'progress' 42 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 43 | reporters: ['mocha', 'karma-typescript'], 44 | 45 | // web server port 46 | port: 9876, 47 | 48 | 49 | // enable / disable colors in the output (reporters and logs) 50 | colors: true, 51 | 52 | 53 | // level of logging 54 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 55 | logLevel: config.LOG_INFO, 56 | 57 | 58 | // enable / disable watching file and executing tests whenever any file changes 59 | autoWatch: true, 60 | 61 | 62 | // start these browsers 63 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 64 | browsers: ['Chrome'], 65 | 66 | 67 | // Continuous Integration mode 68 | // if true, Karma captures browsers, runs the tests and exits 69 | singleRun: false, 70 | 71 | // Concurrency level 72 | // how many browser should be started simultaneous 73 | concurrency: Infinity, 74 | 75 | coverageReporter: { 76 | type : 'text', 77 | dir : 'coverage/' 78 | }, 79 | 80 | customLaunchers: { 81 | 'FirefoxHeadless': { 82 | base: 'Firefox', 83 | flags: [ 84 | '-headless', 85 | ], 86 | } 87 | } 88 | }; 89 | 90 | config.set(configuration); 91 | }; 92 | -------------------------------------------------------------------------------- /src/modules.test.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable only-arrow-functions */ 2 | import { jaspi3xUrl, removeRequire, stubRequire } from '../test/helpers'; 3 | import { loadModules } from './modules'; 4 | // TODO: mock this 5 | import { loadScript } from './script'; 6 | 7 | describe('when loading modules', function() { 8 | const expectedModuleNames = ['esri/map', 'esri/layers/VectorTileLayer']; 9 | describe('when script has been loaded', function() { 10 | beforeEach(function() { 11 | // stub window require 12 | stubRequire(); 13 | }); 14 | it('should have registered an error handler', function(done) { 15 | spyOn(window.require, 'on').and.callThrough(); 16 | loadModules(expectedModuleNames) 17 | .then(() => { 18 | expect(window.require.on.calls.argsFor(0)[0]).toEqual('error'); 19 | done(); 20 | }) 21 | .catch((err) => { 22 | done.fail('call to loadModules should not have failed with: ' + err); 23 | }); 24 | }); 25 | it('should call require w/ correct args', function(done) { 26 | spyOn(window, 'require').and.callThrough(); 27 | loadModules(expectedModuleNames) 28 | .then(() => { 29 | expect(window.require.calls.argsFor(0)[0]).toEqual(expectedModuleNames); 30 | done(); 31 | }) 32 | .catch((err) => { 33 | done.fail('call to loadModules should not have failed with: ' + err); 34 | }); 35 | }); 36 | afterEach(function() { 37 | // clean up 38 | removeRequire(); 39 | }); 40 | }); 41 | describe('when the script has not yet been loaded', function() { 42 | beforeEach(function() { 43 | // uh oh, not sure why this is needed 44 | // seems like some test above did not clean up after itself 45 | // but I can't find where 46 | // TODO: remove this line 47 | removeRequire(); 48 | // w/o it, test fails w/ 49 | // TypeError: Cannot read property 'argsFor' of undefined 50 | // b/c require is defined so it's not trying to add the script 51 | // and doesn't enter the appendChild spyOn() block below 52 | }); 53 | describe('when there has been no attempt to load the script yet', function() { 54 | it('should not reject', function(done) { 55 | spyOn(document.body, 'appendChild').and.callFake(function(el) { 56 | stubRequire(); 57 | spyOn(window, 'require').and.callThrough(); 58 | // trigger the onload event listeners 59 | el.dispatchEvent(new Event('load')); 60 | }); 61 | loadModules(expectedModuleNames, { 62 | url: jaspi3xUrl 63 | }) 64 | .then(() => { 65 | expect(window.require.calls.argsFor(0)[0]).toEqual(expectedModuleNames); 66 | done(); 67 | }) 68 | .catch((err) => { 69 | done.fail('call to loadModules should not have failed with: ' + err); 70 | }); 71 | }); 72 | }); 73 | describe('when the script is still loading', function() { 74 | it('should not reject', function(done) { 75 | let scriptEl; 76 | spyOn(document.body, 'appendChild').and.callFake(function(el) { 77 | scriptEl = el; 78 | stubRequire(); 79 | spyOn(window, 'require').and.callThrough(); 80 | // trigger the onload event listeners 81 | el.dispatchEvent(new Event('load')); 82 | }); 83 | spyOn(document, 'querySelector').and.callFake(function() { 84 | return scriptEl; 85 | }); 86 | // load script using a non-default url 87 | loadScript({ 88 | url: jaspi3xUrl 89 | }); 90 | // don't wait for the script to load before trying to load modules 91 | loadModules(expectedModuleNames) 92 | .then(() => { 93 | expect(window.require.calls.argsFor(0)[0]).toEqual(expectedModuleNames); 94 | done(); 95 | }) 96 | .catch((err) => { 97 | done.fail('call to loadModules should not have failed with: ' + err); 98 | }); 99 | }); 100 | }); 101 | afterEach(function() { 102 | // clean up 103 | removeRequire(); 104 | }); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /src/utils/css.test.ts: -------------------------------------------------------------------------------- 1 | import { loadCss } from './css'; 2 | 3 | describe('when loading the css', () => { 4 | describe('with no arguments', () => { 5 | const url = 'https://js.arcgis.com/4.25/esri/themes/light/main.css'; 6 | let link; 7 | beforeAll(() => { 8 | spyOn(document.head, 'appendChild').and.stub(); 9 | spyOn(document, 'querySelector'); 10 | link = loadCss(); 11 | }); 12 | it('should have checked if the link was already appended', () => { 13 | expect((document.querySelector as jasmine.Spy).calls.argsFor(0)[0]).toEqual(`link[href*="${url}"]`); 14 | }); 15 | it('should have set the href', () => { 16 | expect(link.href).toEqual(url); 17 | }); 18 | it('should not have set the rel', () => { 19 | expect(link.rel).toEqual('stylesheet'); 20 | }); 21 | }); 22 | describe('with a version', () => { 23 | const url = 'https://js.arcgis.com/4.25/esri/themes/light/main.css'; 24 | let link; 25 | beforeAll(() => { 26 | spyOn(document.head, 'appendChild').and.stub(); 27 | spyOn(document, 'querySelector'); 28 | link = loadCss('4.25'); 29 | }); 30 | it('should have checked if the link was already appended', () => { 31 | expect((document.querySelector as jasmine.Spy).calls.argsFor(0)[0]).toEqual(`link[href*="${url}"]`); 32 | }); 33 | it('should have set the href', () => { 34 | expect(link.href).toEqual(url); 35 | }); 36 | it('should not have set the rel', () => { 37 | expect(link.rel).toEqual('stylesheet'); 38 | }); 39 | }); 40 | describe('with "next"', () => { 41 | const url = 'https://js.arcgis.com/next/esri/themes/light/main.css'; 42 | let link; 43 | beforeAll(() => { 44 | spyOn(document.head, 'appendChild').and.stub(); 45 | spyOn(document, 'querySelector'); 46 | link = loadCss('next'); 47 | }); 48 | it('should have checked if the link was already appended', () => { 49 | expect((document.querySelector as jasmine.Spy).calls.argsFor(0)[0]).toEqual(`link[href*="${url}"]`); 50 | }); 51 | it('should have set the href', () => { 52 | expect(link.href).toEqual(url); 53 | }); 54 | it('should not have set the rel', () => { 55 | expect(link.rel).toEqual('stylesheet'); 56 | }); 57 | }); 58 | describe('with a url', () => { 59 | const url = 'http://server/path/to/esri/themes/light/main.css'; 60 | let link; 61 | beforeAll(() => { 62 | spyOn(document.head, 'appendChild').and.stub(); 63 | spyOn(document, 'querySelector'); 64 | link = loadCss(url); 65 | }); 66 | it('should have checked if the link was already appended', () => { 67 | expect((document.querySelector as jasmine.Spy).calls.argsFor(0)[0]).toEqual(`link[href*="${url}"]`); 68 | }); 69 | it('should have set the href', () => { 70 | expect(link.href).toEqual(url); 71 | }); 72 | it('should not have set the rel', () => { 73 | expect(link.rel).toEqual('stylesheet'); 74 | }); 75 | }); 76 | describe('when called twice', () => { 77 | describe('when loading the same url', () => { 78 | const url = 'https://js.arcgis.com/4.25/esri/themes/light/main.css'; 79 | let link; 80 | let link2; 81 | beforeAll(() => { 82 | spyOn(document.head, 'appendChild').and.stub(); 83 | link = loadCss(url); 84 | spyOn(document, 'querySelector').and.returnValue(link); 85 | link2 = loadCss(url); 86 | }); 87 | it('should return the link if it is already loaded', () => { 88 | expect(link2).toEqual(link); 89 | }); 90 | it('should not have tried to append the link a second time', () => { 91 | expect((document.head.appendChild as jasmine.Spy).calls.count()).toEqual(1); 92 | }); 93 | }); 94 | }); 95 | describe('when inserting before an existing node', () => { 96 | const url = 'https://js.arcgis.com/4.25/esri/themes/light/main.css'; 97 | // insert before the first