├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── dist
├── moon-router.js
└── moon-router.min.js
├── gulpfile.js
├── package.json
├── src
├── index.js
├── util
│ ├── components.js
│ ├── constants.js
│ ├── map.js
│ ├── run.js
│ └── setup.js
└── wrapper.js
└── test
├── core
├── route
│ └── route.js
└── util.js
├── karma.conf.js
└── test.html
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | script: npm run test
3 | node_js:
4 | - "node"
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2018 Kabir Shah
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Moon Router
2 |
3 | A router plugin for Moon.
4 |
5 | [](https://travis-ci.org/kbrsh/moon-router)
6 |
7 | ### What?
8 |
9 | Moon router lets you create routes that map to different components. Clicking special links update the view and the URL as if the user is visiting a new page.
10 |
11 | ### Install
12 |
13 | With npm:
14 |
15 | ```bash
16 | $ npm install moon-router
17 | ```
18 |
19 | ```js
20 | const MoonRouter = require("moon-router");
21 | Moon.use(MoonRouter)
22 | ```
23 |
24 | With a CDN/Local:
25 |
26 | ```html
27 |
28 |
31 | ```
32 |
33 | ### Usage
34 |
35 | Initialize Moon router
36 |
37 | ```js
38 | Moon.use(MoonRouter)
39 | ```
40 |
41 | #### Creating Routes
42 |
43 | **Before** you create your Moon instance, define your routes like this:
44 |
45 | ```js
46 | const router = new MoonRouter({
47 | default: "/",
48 | map: {
49 | "/": "Root",
50 | "/hello": "Hello"
51 | }
52 | });
53 | ```
54 |
55 | This will map `/` to the `Root` component, and will map `/hello` to the `Hello` component.
56 |
57 | The `default` route is `/`, if a URL is not found, Moon will display this route.
58 |
59 | ##### History Mode
60 |
61 | Moon Router will use "hash" mode by default, meaning the URL will look something like: `/#/`. If you want routes to look more realistic, you must provide a `mode` option.
62 |
63 | ```js
64 | const router = new MoonRouter({
65 | default: "/",
66 | map: {
67 | "/": "Root",
68 | "/hello": "Hello"
69 | },
70 | mode: "history"
71 | });
72 | ```
73 |
74 | Still, if a user visits `"/hello"` in history mode, they will get a 404 response. Moon Router can only switch routes in history mode, not initialize them. For this, you must configure your server to always serve a single page but still keep the route.
75 |
76 | ##### Dynamic Routes
77 |
78 | Routes can also be dynamic, with support for query parameters, named parameters, and wildcards. These can be accessed via a `route` prop passed to the view component.
79 |
80 | ```js
81 | const router = new MoonRouter({
82 | map: {
83 | "/:named": "Root", // `named` can be shown with {{route.params.named}}
84 | "/:other/parameter/that/is/:named": "Named",
85 | "/*": "Wildcard" // matches any ONE path
86 | }
87 | });
88 | ```
89 |
90 | * Named Parameters are in the `route.params` object
91 | * Query Parameters are in the `route.query` object (`/?key=val`)
92 |
93 | Just remember, to access the special `route` variable, you must state it is a prop in the component, like:
94 |
95 | ```js
96 | Moon.component("Named", {
97 | props: ['route'],
98 | template: '
'
99 | });
100 | ```
101 |
102 | #### Define Components
103 |
104 | After initializing Moon Router, define any components referenced.
105 |
106 | ```js
107 | Moon.component("Root", {
108 | template: `
109 |
Welcome to "/"
110 | To /hello
111 | `
112 | });
113 |
114 | Moon.component("Hello", {
115 | template: `
116 |
You have Reached "/hello"
117 | Back Home
118 | `
119 | });
120 | ```
121 |
122 | You will notice the `router-link` component. This is by default, rendered as an `a` tag, and should **always be used** to link to routes. A class of `router-link-active` will be applied to the active link by default, unless another class is provided in `options.activeClass`.
123 |
124 | When clicking on this link, the user will be shown the new route at the `router-view` component (see below), and the page will not reload.
125 |
126 | #### Installing Router to Instance
127 |
128 | When creating your Moon instance, call the `init` method on the router inside of the `mounted` hook to bind the instance to the router.
129 |
130 | ```js
131 | new Moon({
132 | el: "#app",
133 | hooks: {
134 | mounted() {
135 | router.init(this);
136 | }
137 | }
138 | });
139 | ```
140 |
141 | ```html
142 |
143 |
144 |
145 | ```
146 |
147 | This will install the Moon Router to the Moon Instance, and when you visit the page, you will notice the URL changes to `/#/`
148 |
149 | The `router-view` is a `span` with a child component that corresponds with the current route.
150 |
151 | ### License
152 |
153 | Licensed under the [MIT License](https://kbrsh.github.io/license) By [Kabir Shah](https://kabir.ml)
154 |
--------------------------------------------------------------------------------
/dist/moon-router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Moon Router v0.1.3
3 | * Copyright 2016-2018 Kabir Shah
4 | * Released under the MIT License
5 | * https://github.com/kbrsh/moon-router
6 | */
7 |
8 | (function(root, factory) {
9 | /* ======= Global Moon Router ======= */
10 | if(typeof module === "undefined") {
11 | root.MoonRouter = factory();
12 | } else {
13 | module.exports = factory();
14 | }
15 | }(this, function() {
16 | var Moon;
17 |
18 | var wildcardAlias = "*";
19 | var queryAlias = "?";
20 | var namedParameterAlias = ":";
21 | var componentAlias = "@";
22 |
23 | var setup = function (instance, mode) {
24 | var getPath;
25 | var navigate;
26 | var custom = false;
27 |
28 | if(mode === undefined || mode === "hash") {
29 | // Setup Path Getter
30 | getPath = function() {
31 | return window.location.hash.substring(1);
32 | }
33 |
34 | // Create navigation function
35 | navigate = function(route) {
36 | window.location.hash = '#' + route;
37 | run(instance, route);
38 | }
39 |
40 | // Add hash change listener
41 | window.addEventListener("hashchange", function() {
42 | run(instance, instance.getPath());
43 | });
44 | } else if(mode === "history") {
45 | // Setup Path Getter
46 | getPath = function() {
47 | return window.location.pathname;
48 | }
49 |
50 | // Create navigation function
51 | navigate = function(route) {
52 | history.pushState(null, null, route);
53 | run(instance, route);
54 | }
55 |
56 | // Create listener
57 | custom = true;
58 | window.addEventListener("popstate", function() {
59 | run(instance, instance.getPath());
60 | });
61 | }
62 |
63 | instance.getPath = getPath;
64 | instance.navigate = navigate;
65 | instance.custom = custom;
66 | }
67 |
68 | var registerComponents = function (instance, Moon) {
69 | // Router View component
70 | Moon.extend("router-view", {
71 | data: function() {
72 | return {
73 | component: undefined
74 | }
75 | },
76 | render: function(m) {
77 | var currentComponent = this.get("component");
78 | var children;
79 |
80 | if(currentComponent === undefined) {
81 | children = [];
82 | } else {
83 | children = [m(currentComponent, {attrs: {route: instance.route}}, {}, [])];
84 | }
85 |
86 | return m("span", {}, {}, children);
87 | },
88 | hooks: {
89 | init: function init() {
90 | instance.components.push(this);
91 | }
92 | }
93 | });
94 |
95 | // Router Link component
96 | Moon.extend("router-link", {
97 | props: ["to"],
98 | render: function(m) {
99 | var to = this.get("to");
100 | var attrs = {};
101 | var meta = {};
102 |
103 | var same = instance.current === to;
104 |
105 | if(instance.custom === true) {
106 | attrs.href = to;
107 | meta.events = {
108 | "click": [function(event) {
109 | event.preventDefault();
110 | if(same === false) {
111 | instance.navigate(to);
112 | }
113 | }]
114 | };
115 | } else {
116 | attrs.href = '#' + to;
117 | }
118 |
119 | if(same === true) {
120 | attrs["class"] = instance.activeClass;
121 | }
122 |
123 | return m('a', {attrs: attrs}, meta, this.insert);
124 | },
125 | hooks: {
126 | init: function init$1() {
127 | instance.components.push(this);
128 | }
129 | }
130 | });
131 | }
132 |
133 | var map = function (routes) {
134 | var routesMap = {};
135 |
136 | for(var route in routes) {
137 | var currentMapState = routesMap;
138 |
139 | // Split up by parts
140 | var parts = route.substring(1).split("/");
141 | for(var i = 0; i < parts.length; i++) {
142 | var part = parts[i];
143 |
144 | // Found named parameter
145 | if(part[0] === namedParameterAlias) {
146 | var param = currentMapState[namedParameterAlias];
147 | if(param === undefined) {
148 | currentMapState[namedParameterAlias] = {
149 | name: part.substring(1)
150 | };
151 | } else {
152 | param.name = part.substring(1);
153 | }
154 |
155 | currentMapState = currentMapState[namedParameterAlias];
156 | } else {
157 | // Add part to map
158 | if(currentMapState[part] === undefined) {
159 | currentMapState[part] = {};
160 | }
161 |
162 | currentMapState = currentMapState[part];
163 | }
164 | }
165 |
166 | // Add component
167 | currentMapState["@"] = routes[route];
168 | }
169 |
170 | return routesMap;
171 | }
172 |
173 | var run = function (instance, path) {
174 | // Change current component and build
175 | var parts = path.slice(1).split("/");
176 | var currentMapState = instance.map;
177 | var context = {
178 | query: {},
179 | params: {}
180 | }
181 |
182 | for(var i = 0; i < parts.length; i++) {
183 | var part = parts[i];
184 |
185 | // Query Parameters
186 | if(part.indexOf(queryAlias) !== -1) {
187 | var splitQuery = part.split(queryAlias);
188 | part = splitQuery.shift();
189 |
190 | for(var j = 0; j < splitQuery.length; j++) {
191 | var keyVal = splitQuery[j].split('=');
192 | context.query[keyVal[0]] = keyVal[1];
193 | }
194 | }
195 |
196 | if(currentMapState[part] === undefined) {
197 | var namedParameter = currentMapState[namedParameterAlias];
198 |
199 | if(namedParameter !== undefined) {
200 | // Named Parameter
201 | context.params[namedParameter.name] = part;
202 | part = namedParameterAlias;
203 | } else if(currentMapState[wildcardAlias] !== undefined) {
204 | // Wildcard
205 | part = wildcardAlias;
206 | }
207 | }
208 |
209 | // Move through state
210 | currentMapState = currentMapState[part];
211 |
212 | // Path not in map
213 | if(currentMapState === undefined) {
214 | run(instance, instance.default);
215 | return false;
216 | }
217 | }
218 |
219 | // Handler not in map
220 | if(currentMapState[componentAlias] === undefined) {
221 | run(instance, instance.default);
222 | return false;
223 | }
224 |
225 | // Setup current information
226 | instance.current = path;
227 |
228 | // Setup Route Context
229 | instance.route = context;
230 |
231 | // Build Instance
232 | instance.instance.build();
233 |
234 | // Build components
235 | var component = currentMapState[componentAlias];
236 | var components = instance.components;
237 | for(var i$1 = 0; i$1 < components.length; i$1++) {
238 | components[i$1].set("component", component);
239 | }
240 |
241 | return true;
242 | }
243 |
244 |
245 | function MoonRouter(options) {
246 | // Instance
247 | this.instance = undefined;
248 |
249 | // Default route
250 | var defaultRoute = options["default"];
251 | if(defaultRoute === undefined) {
252 | this["default"] = "/";
253 | } else {
254 | this["default"] = defaultRoute;
255 | }
256 |
257 | // Route to component map
258 | var providedMap = options.map;
259 | if(providedMap === undefined) {
260 | this.map = {};
261 | } else {
262 | this.map = map(providedMap);
263 | }
264 |
265 | // Route context
266 | this.route = {};
267 |
268 | // Components
269 | this.components = [];
270 |
271 | // Active class
272 | var activeClass = options.activeClass;
273 | if(activeClass === undefined) {
274 | this.activeClass = "router-link-active";
275 | } else {
276 | this.activeClass = activeClass;
277 | }
278 |
279 | // Register components
280 | registerComponents(this, Moon);
281 |
282 | // Initialize route
283 | setup(this, options.mode);
284 | }
285 |
286 | // Install router to instance
287 | MoonRouter.prototype.init = function(instance) {
288 | this.instance = instance;
289 | this.navigate(this.getPath());
290 | }
291 |
292 | // Plugin init
293 | MoonRouter.init = function (_Moon) {
294 | Moon = _Moon;
295 | }
296 |
297 | return MoonRouter;
298 | }));
299 |
--------------------------------------------------------------------------------
/dist/moon-router.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Moon Router v0.1.3
3 | * Copyright 2016-2018 Kabir Shah
4 | * Released under the MIT License
5 | * https://github.com/kbrsh/moon-router
6 | */
7 | !function(t,n){"undefined"==typeof module?t.MoonRouter=n():module.exports=n()}(this,function(){function t(t){this.instance=void 0;var i=t.default;void 0===i?this.default="/":this.default=i;var e=t.map;void 0===e?this.map={}:this.map=u(e),this.route={},this.components=[];var o=t.activeClass;void 0===o?this.activeClass="router-link-active":this.activeClass=o,s(this,n),a(this,t.mode)}var n,i="*",e="?",o=":",r="@",a=function(t,n){var i,e,o=!1;void 0===n||"hash"===n?(i=function(){return window.location.hash.substring(1)},e=function(n){window.location.hash="#"+n,c(t,n)},window.addEventListener("hashchange",function(){c(t,t.getPath())})):"history"===n&&(i=function(){return window.location.pathname},e=function(n){history.pushState(null,null,n),c(t,n)},o=!0,window.addEventListener("popstate",function(){c(t,t.getPath())})),t.getPath=i,t.navigate=e,t.custom=o},s=function(t,n){n.extend("router-view",{data:function(){return{component:void 0}},render:function(n){var i,e=this.get("component");return i=void 0===e?[]:[n(e,{attrs:{route:t.route}},{},[])],n("span",{},{},i)},hooks:{init:function(){t.components.push(this)}}}),n.extend("router-link",{props:["to"],render:function(n){var i=this.get("to"),e={},o={},r=t.current===i;return t.custom===!0?(e.href=i,o.events={click:[function(n){n.preventDefault(),r===!1&&t.navigate(i)}]}):e.href="#"+i,r===!0&&(e.class=t.activeClass),n("a",{attrs:e},o,this.insert)},hooks:{init:function(){t.components.push(this)}}})},u=function(t){var n={};for(var i in t){for(var e=n,r=i.substring(1).split("/"),a=0;a {
58 | Moon = _Moon;
59 | }
60 |
--------------------------------------------------------------------------------
/src/util/components.js:
--------------------------------------------------------------------------------
1 | const registerComponents = (instance, Moon) => {
2 | // Router View component
3 | Moon.extend("router-view", {
4 | data: function() {
5 | return {
6 | component: undefined
7 | }
8 | },
9 | render: function(m) {
10 | const currentComponent = this.get("component");
11 | let children;
12 |
13 | if(currentComponent === undefined) {
14 | children = [];
15 | } else {
16 | children = [m(currentComponent, {attrs: {route: instance.route}}, {}, [])];
17 | }
18 |
19 | return m("span", {}, {}, children);
20 | },
21 | hooks: {
22 | init() {
23 | instance.components.push(this);
24 | }
25 | }
26 | });
27 |
28 | // Router Link component
29 | Moon.extend("router-link", {
30 | props: ["to"],
31 | render: function(m) {
32 | const to = this.get("to");
33 | let attrs = {};
34 | let meta = {};
35 |
36 | const same = instance.current === to;
37 |
38 | if(instance.custom === true) {
39 | attrs.href = to;
40 | meta.events = {
41 | "click": [function(event) {
42 | event.preventDefault();
43 | if(same === false) {
44 | instance.navigate(to);
45 | }
46 | }]
47 | };
48 | } else {
49 | attrs.href = '#' + to;
50 | }
51 |
52 | if(same === true) {
53 | attrs["class"] = instance.activeClass;
54 | }
55 |
56 | return m('a', {attrs: attrs}, meta, this.insert);
57 | },
58 | hooks: {
59 | init() {
60 | instance.components.push(this);
61 | }
62 | }
63 | });
64 | }
65 |
--------------------------------------------------------------------------------
/src/util/constants.js:
--------------------------------------------------------------------------------
1 | const wildcardAlias = "*";
2 | const queryAlias = "?";
3 | const namedParameterAlias = ":";
4 | const componentAlias = "@";
5 |
--------------------------------------------------------------------------------
/src/util/map.js:
--------------------------------------------------------------------------------
1 | const map = (routes) => {
2 | let routesMap = {};
3 |
4 | for(let route in routes) {
5 | let currentMapState = routesMap;
6 |
7 | // Split up by parts
8 | const parts = route.substring(1).split("/");
9 | for(let i = 0; i < parts.length; i++) {
10 | let part = parts[i];
11 |
12 | // Found named parameter
13 | if(part[0] === namedParameterAlias) {
14 | let param = currentMapState[namedParameterAlias];
15 | if(param === undefined) {
16 | currentMapState[namedParameterAlias] = {
17 | name: part.substring(1)
18 | };
19 | } else {
20 | param.name = part.substring(1);
21 | }
22 |
23 | currentMapState = currentMapState[namedParameterAlias];
24 | } else {
25 | // Add part to map
26 | if(currentMapState[part] === undefined) {
27 | currentMapState[part] = {};
28 | }
29 |
30 | currentMapState = currentMapState[part];
31 | }
32 | }
33 |
34 | // Add component
35 | currentMapState["@"] = routes[route];
36 | }
37 |
38 | return routesMap;
39 | }
40 |
--------------------------------------------------------------------------------
/src/util/run.js:
--------------------------------------------------------------------------------
1 | const run = (instance, path) => {
2 | // Change current component and build
3 | const parts = path.slice(1).split("/");
4 | let currentMapState = instance.map;
5 | let context = {
6 | query: {},
7 | params: {}
8 | }
9 |
10 | for(let i = 0; i < parts.length; i++) {
11 | let part = parts[i];
12 |
13 | // Query Parameters
14 | if(part.indexOf(queryAlias) !== -1) {
15 | const splitQuery = part.split(queryAlias);
16 | part = splitQuery.shift();
17 |
18 | for(let j = 0; j < splitQuery.length; j++) {
19 | const keyVal = splitQuery[j].split('=');
20 | context.query[keyVal[0]] = keyVal[1];
21 | }
22 | }
23 |
24 | if(currentMapState[part] === undefined) {
25 | let namedParameter = currentMapState[namedParameterAlias];
26 |
27 | if(namedParameter !== undefined) {
28 | // Named Parameter
29 | context.params[namedParameter.name] = part;
30 | part = namedParameterAlias;
31 | } else if(currentMapState[wildcardAlias] !== undefined) {
32 | // Wildcard
33 | part = wildcardAlias;
34 | }
35 | }
36 |
37 | // Move through state
38 | currentMapState = currentMapState[part];
39 |
40 | // Path not in map
41 | if(currentMapState === undefined) {
42 | run(instance, instance.default);
43 | return false;
44 | }
45 | }
46 |
47 | // Handler not in map
48 | if(currentMapState[componentAlias] === undefined) {
49 | run(instance, instance.default);
50 | return false;
51 | }
52 |
53 | // Setup current information
54 | instance.current = path;
55 |
56 | // Setup Route Context
57 | instance.route = context;
58 |
59 | // Build Instance
60 | instance.instance.build();
61 |
62 | // Build components
63 | const component = currentMapState[componentAlias];
64 | const components = instance.components;
65 | for(let i = 0; i < components.length; i++) {
66 | components[i].set("component", component);
67 | }
68 |
69 | return true;
70 | }
71 |
--------------------------------------------------------------------------------
/src/util/setup.js:
--------------------------------------------------------------------------------
1 | const setup = (instance, mode) => {
2 | let getPath;
3 | let navigate;
4 | let custom = false;
5 |
6 | if(mode === undefined || mode === "hash") {
7 | // Setup Path Getter
8 | getPath = function() {
9 | return window.location.hash.substring(1);
10 | }
11 |
12 | // Create navigation function
13 | navigate = function(route) {
14 | window.location.hash = '#' + route;
15 | run(instance, route);
16 | }
17 |
18 | // Add hash change listener
19 | window.addEventListener("hashchange", function() {
20 | run(instance, instance.getPath());
21 | });
22 | } else if(mode === "history") {
23 | // Setup Path Getter
24 | getPath = function() {
25 | return window.location.pathname;
26 | }
27 |
28 | // Create navigation function
29 | navigate = function(route) {
30 | history.pushState(null, null, route);
31 | run(instance, route);
32 | }
33 |
34 | // Create listener
35 | custom = true;
36 | window.addEventListener("popstate", function() {
37 | run(instance, instance.getPath());
38 | });
39 | }
40 |
41 | instance.getPath = getPath;
42 | instance.navigate = navigate;
43 | instance.custom = custom;
44 | }
45 |
--------------------------------------------------------------------------------
/src/wrapper.js:
--------------------------------------------------------------------------------
1 | (function(root, factory) {
2 | /* ======= Global Moon Router ======= */
3 | if(typeof module === "undefined") {
4 | root.MoonRouter = factory();
5 | } else {
6 | module.exports = factory();
7 | }
8 | }(this, function() {
9 | //=require ../dist/moon-router.js
10 | return MoonRouter;
11 | }));
12 |
--------------------------------------------------------------------------------
/test/core/route/route.js:
--------------------------------------------------------------------------------
1 | describe("Route", function() {
2 | var historyDone = [false, false, false, false];
3 |
4 | describe("History Mode", function() {
5 | var el = createTestElement("history", " ");
6 | var component = null;
7 |
8 | Moon.extend("Root", {
9 | template: "Root Route {{msg}} ",
10 | data: function() {
11 | return {
12 | msg: "Message"
13 | }
14 | },
15 | hooks: {
16 | mounted: function() {
17 | component = this;
18 | }
19 | }
20 | });
21 |
22 | Moon.extend("Test", {
23 | template: "Test Route "
24 | });
25 |
26 | var router = new MoonRouter({
27 | "default": "/context.html",
28 | "map": {
29 | "/context.html": "Root",
30 | "/context.html/test": "Test"
31 | },
32 | "mode": "history"
33 | });
34 |
35 | var app = new Moon({
36 | root: "#history",
37 | hooks: {
38 | mounted: function() {
39 | router.init(this);
40 | }
41 | }
42 | });
43 |
44 | it("should initialize a router view", function() {
45 | return wait(function() {
46 | expect(el.firstChild.nextSibling.firstChild.nodeName).to.equal("H1");
47 | expect(el.firstChild.nextSibling.firstChild.innerHTML).to.equal("Root Route Message");
48 | historyDone[0] = true;
49 | });
50 | });
51 |
52 | it("should update with data", function() {
53 | component.set("msg", "Changed");
54 | return wait(function() {
55 | expect(el.firstChild.nextSibling.firstChild.innerHTML).to.equal("Root Route Changed");
56 | historyDone[1] = true;
57 | });
58 | });
59 |
60 | it("should navigate with router link", function() {
61 | el.firstChild.click();
62 | return wait(function() {
63 | expect(el.firstChild.nextSibling.firstChild.innerHTML).to.equal("Test Route");
64 | expect(el.firstChild.getAttribute("class")).to.equal("router-link-active");
65 | historyDone[2] = true;
66 | });
67 | });
68 |
69 | it("should navigate from code", function() {
70 | expect(el.firstChild.getAttribute("class")).to.equal("router-link-active");
71 | router.navigate("/context.html");
72 | return wait(function() {
73 | expect(el.firstChild.nextSibling.firstChild.innerHTML).to.equal("Root Route Message");
74 | expect(el.firstChild.getAttribute("class")).to.equal(null);
75 | historyDone[3] = true;
76 | });
77 | });
78 | });
79 |
80 | describe("Hash Mode", function() {
81 | var el = null;
82 | var component = null;
83 | var router = null
84 | var app = null;
85 |
86 | // Poll to ensure history tests are done
87 | var checkHistory = function(done) {
88 | if(historyDone[0] === true && historyDone[1] === true && historyDone[2] === true && historyDone[3] === true) {
89 | window.removeEventListener("popstate");
90 | done();
91 | } else {
92 | setInterval(function() {
93 | checkHistory(done);
94 | }, 500);
95 | }
96 | }
97 |
98 | before(function(done) {
99 | checkHistory(function() {
100 | el = createTestElement("route", " ");
101 | component = null;
102 |
103 | Moon.extend("Root", {
104 | template: "Root Route {{msg}} ",
105 | data: function() {
106 | return {
107 | msg: "Message"
108 | }
109 | },
110 | hooks: {
111 | mounted: function() {
112 | component = this;
113 | }
114 | }
115 | });
116 |
117 | Moon.extend("Test", {
118 | props: ["route"],
119 | template: "Test Route {{route.query.queryParam}} {{route.params.namedParam}} "
120 | });
121 |
122 | router = new MoonRouter({
123 | "default": "/",
124 | "map": {
125 | "/": "Root",
126 | "/test/*/:namedParam": "Test"
127 | }
128 | });
129 |
130 | app = new Moon({
131 | root: "#route",
132 | hooks: {
133 | mounted: function() {
134 | router.init(this);
135 | }
136 | }
137 | });
138 |
139 | done();
140 | });
141 | });
142 |
143 | it("should initialize a router view", function() {
144 | return wait(function() {
145 | expect(el.firstChild.nextSibling.firstChild.nodeName).to.equal("H1");
146 | expect(el.firstChild.nextSibling.firstChild.innerHTML).to.equal("Root Route Message");
147 | });
148 | });
149 |
150 | it("should update with data", function() {
151 | component.set("msg", "Changed");
152 | return wait(function() {
153 | expect(el.firstChild.nextSibling.firstChild.innerHTML).to.equal("Root Route Changed");
154 | });
155 | });
156 |
157 | it("should navigate with router link", function() {
158 | expect(el.firstChild.getAttribute("class")).to.equal(null);
159 | el.firstChild.click();
160 | return wait(function() {
161 | expect(el.firstChild.nextSibling.firstChild.innerHTML).to.equal("Test Route true named");
162 | expect(el.firstChild.getAttribute("class")).to.equal("router-link-active");
163 | });
164 | });
165 |
166 | it("should navigate from code", function() {
167 | expect(el.firstChild.getAttribute("class")).to.equal("router-link-active");
168 | router.navigate("/");
169 | return wait(function() {
170 | expect(el.firstChild.nextSibling.firstChild.innerHTML).to.equal("Root Route Message");
171 | expect(el.firstChild.getAttribute("class")).to.equal(null);
172 | });
173 | });
174 | });
175 | });
176 |
--------------------------------------------------------------------------------
/test/core/util.js:
--------------------------------------------------------------------------------
1 | Moon.use(MoonRouter);
2 |
3 | var expect = chai.expect;
4 |
5 | if(document.getElementById("els")) {
6 | var els = document.getElementById("els");
7 | } else {
8 | var els = document.createElement("div");
9 | els.id = "els";
10 | document.body.appendChild(els);
11 | }
12 |
13 | var createTestElement = function(id, html) {
14 | var el = document.createElement("div");
15 | el.setAttribute("m-mask", "");
16 | el.innerHTML = html;
17 | el.id = id;
18 | els.appendChild(el);
19 | return el;
20 | }
21 |
22 | // Promise Polyfill
23 | !function(e){function n(){}function t(e,n){return function(){e.apply(n,arguments)}}function o(e){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],s(e,this)}function i(e,n){for(;3===e._state;)e=e._value;return 0===e._state?void e._deferreds.push(n):(e._handled=!0,void o._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null===t)return void(1===e._state?r:u)(n.promise,e._value);var o;try{o=t(e._value)}catch(i){return void u(n.promise,i)}r(n.promise,o)}))}function r(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var i=n.then;if(n instanceof o)return e._state=3,e._value=n,void f(e);if("function"==typeof i)return void s(t(i,n),e)}e._state=1,e._value=n,f(e)}catch(r){u(e,r)}}function u(e,n){e._state=2,e._value=n,f(e)}function f(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var n=0,t=e._deferreds.length;n
2 |
3 |
4 |
5 | Moon Router | Test
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
--------------------------------------------------------------------------------