@angular-wave/angular.ts 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +1 -0
- package/.eslintrc.cjs +29 -0
- package/.github/workflows/playwright.yml +27 -0
- package/CHANGELOG.md +17974 -0
- package/CODE_OF_CONDUCT.md +3 -0
- package/CONTRIBUTING.md +246 -0
- package/DEVELOPERS.md +488 -0
- package/LICENSE +22 -0
- package/Makefile +31 -0
- package/README.md +115 -0
- package/RELEASE.md +98 -0
- package/SECURITY.md +16 -0
- package/TRIAGING.md +135 -0
- package/css/angular.css +22 -0
- package/dist/angular-ts.cjs.js +36843 -0
- package/dist/angular-ts.esm.js +36841 -0
- package/dist/angular-ts.umd.js +36848 -0
- package/dist/build/angular-animate.js +4272 -0
- package/dist/build/angular-aria.js +426 -0
- package/dist/build/angular-message-format.js +1072 -0
- package/dist/build/angular-messages.js +829 -0
- package/dist/build/angular-mocks.js +3757 -0
- package/dist/build/angular-parse-ext.js +1275 -0
- package/dist/build/angular-resource.js +911 -0
- package/dist/build/angular-route.js +1266 -0
- package/dist/build/angular-sanitize.js +891 -0
- package/dist/build/angular-touch.js +368 -0
- package/dist/build/angular.js +36600 -0
- package/e2e/unit.spec.ts +15 -0
- package/images/android-chrome-192x192.png +0 -0
- package/images/android-chrome-512x512.png +0 -0
- package/images/apple-touch-icon.png +0 -0
- package/images/favicon-16x16.png +0 -0
- package/images/favicon-32x32.png +0 -0
- package/images/favicon.ico +0 -0
- package/images/site.webmanifest +1 -0
- package/index.html +104 -0
- package/package.json +47 -0
- package/playwright.config.ts +78 -0
- package/public/circle.html +1 -0
- package/public/my_child_directive.html +1 -0
- package/public/my_directive.html +1 -0
- package/public/my_other_directive.html +1 -0
- package/public/test.html +1 -0
- package/rollup.config.js +31 -0
- package/src/animations/animateCache.js +55 -0
- package/src/animations/animateChildrenDirective.js +105 -0
- package/src/animations/animateCss.js +1139 -0
- package/src/animations/animateCssDriver.js +291 -0
- package/src/animations/animateJs.js +367 -0
- package/src/animations/animateJsDriver.js +67 -0
- package/src/animations/animateQueue.js +851 -0
- package/src/animations/animation.js +506 -0
- package/src/animations/module.js +779 -0
- package/src/animations/ngAnimateSwap.js +119 -0
- package/src/animations/rafScheduler.js +50 -0
- package/src/animations/shared.js +378 -0
- package/src/constants.js +20 -0
- package/src/core/animate.js +845 -0
- package/src/core/animateCss.js +73 -0
- package/src/core/animateRunner.js +195 -0
- package/src/core/attributes.js +199 -0
- package/src/core/cache.js +45 -0
- package/src/core/compile.js +4727 -0
- package/src/core/controller.js +225 -0
- package/src/core/exceptionHandler.js +63 -0
- package/src/core/filter.js +146 -0
- package/src/core/interpolate.js +442 -0
- package/src/core/interval.js +188 -0
- package/src/core/intervalFactory.js +57 -0
- package/src/core/location.js +1086 -0
- package/src/core/parser/parse.js +2562 -0
- package/src/core/parser/parse.md +13 -0
- package/src/core/q.js +746 -0
- package/src/core/rootScope.js +1596 -0
- package/src/core/sanitizeUri.js +85 -0
- package/src/core/sce.js +1161 -0
- package/src/core/taskTrackerFactory.js +125 -0
- package/src/core/timeout.js +121 -0
- package/src/core/urlUtils.js +187 -0
- package/src/core/utils.js +1349 -0
- package/src/directive/a.js +37 -0
- package/src/directive/attrs.js +283 -0
- package/src/directive/bind.js +51 -0
- package/src/directive/bind.md +142 -0
- package/src/directive/change.js +12 -0
- package/src/directive/change.md +25 -0
- package/src/directive/cloak.js +12 -0
- package/src/directive/cloak.md +24 -0
- package/src/directive/events.js +75 -0
- package/src/directive/events.md +166 -0
- package/src/directive/form.js +725 -0
- package/src/directive/init.js +15 -0
- package/src/directive/init.md +41 -0
- package/src/directive/input.js +1783 -0
- package/src/directive/list.js +46 -0
- package/src/directive/list.md +22 -0
- package/src/directive/ngClass.js +249 -0
- package/src/directive/ngController.js +64 -0
- package/src/directive/ngCsp.js +82 -0
- package/src/directive/ngIf.js +134 -0
- package/src/directive/ngInclude.js +217 -0
- package/src/directive/ngModel.js +1356 -0
- package/src/directive/ngModelOptions.js +509 -0
- package/src/directive/ngOptions.js +670 -0
- package/src/directive/ngRef.js +90 -0
- package/src/directive/ngRepeat.js +650 -0
- package/src/directive/ngShowHide.js +255 -0
- package/src/directive/ngSwitch.js +178 -0
- package/src/directive/ngTransclude.js +98 -0
- package/src/directive/non-bindable.js +11 -0
- package/src/directive/non-bindable.md +17 -0
- package/src/directive/script.js +30 -0
- package/src/directive/select.js +624 -0
- package/src/directive/style.js +25 -0
- package/src/directive/style.md +23 -0
- package/src/directive/validators.js +329 -0
- package/src/exts/aria.js +544 -0
- package/src/exts/messages.js +852 -0
- package/src/filters/filter.js +207 -0
- package/src/filters/filter.md +69 -0
- package/src/filters/filters.js +239 -0
- package/src/filters/json.md +16 -0
- package/src/filters/limit-to.js +43 -0
- package/src/filters/limit-to.md +19 -0
- package/src/filters/order-by.js +183 -0
- package/src/filters/order-by.md +83 -0
- package/src/index.js +13 -0
- package/src/injector.js +1034 -0
- package/src/jqLite.js +1117 -0
- package/src/loader.js +1320 -0
- package/src/public.js +215 -0
- package/src/routeToRegExp.js +41 -0
- package/src/services/anchorScroll.js +135 -0
- package/src/services/browser.js +321 -0
- package/src/services/cacheFactory.js +398 -0
- package/src/services/cookieReader.js +72 -0
- package/src/services/document.js +64 -0
- package/src/services/http.js +1537 -0
- package/src/services/httpBackend.js +206 -0
- package/src/services/log.js +160 -0
- package/src/services/templateRequest.js +139 -0
- package/test/angular.spec.js +2153 -0
- package/test/aria/aria.spec.js +1245 -0
- package/test/binding.spec.js +504 -0
- package/test/build-test.html +14 -0
- package/test/injector.spec.js +2327 -0
- package/test/jasmine/jasmine-5.1.2/boot0.js +65 -0
- package/test/jasmine/jasmine-5.1.2/boot1.js +133 -0
- package/test/jasmine/jasmine-5.1.2/jasmine-html.js +963 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.css +320 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.js +10824 -0
- package/test/jasmine/jasmine-5.1.2/jasmine_favicon.png +0 -0
- package/test/jasmine/jasmine-browser.json +17 -0
- package/test/jasmine/jasmine.json +9 -0
- package/test/jqlite.spec.js +2133 -0
- package/test/loader.spec.js +219 -0
- package/test/messages/messages.spec.js +1146 -0
- package/test/min-err.spec.js +174 -0
- package/test/mock-test.html +13 -0
- package/test/module-test.html +15 -0
- package/test/ng/anomate.spec.js +606 -0
- package/test/ng/cache-factor.spec.js +334 -0
- package/test/ng/compile.spec.js +17956 -0
- package/test/ng/controller-provider.spec.js +227 -0
- package/test/ng/cookie-reader.spec.js +98 -0
- package/test/ng/directive/a.spec.js +192 -0
- package/test/ng/directive/bind.spec.js +334 -0
- package/test/ng/directive/boolean.spec.js +136 -0
- package/test/ng/directive/change.spec.js +71 -0
- package/test/ng/directive/class.spec.js +858 -0
- package/test/ng/directive/click.spec.js +38 -0
- package/test/ng/directive/cloak.spec.js +44 -0
- package/test/ng/directive/constoller.spec.js +194 -0
- package/test/ng/directive/element-style.spec.js +92 -0
- package/test/ng/directive/event.spec.js +282 -0
- package/test/ng/directive/form.spec.js +1518 -0
- package/test/ng/directive/href.spec.js +143 -0
- package/test/ng/directive/if.spec.js +402 -0
- package/test/ng/directive/include.spec.js +828 -0
- package/test/ng/directive/init.spec.js +68 -0
- package/test/ng/directive/input.spec.js +3810 -0
- package/test/ng/directive/list.spec.js +170 -0
- package/test/ng/directive/model-options.spec.js +1008 -0
- package/test/ng/directive/model.spec.js +1905 -0
- package/test/ng/directive/non-bindable.spec.js +55 -0
- package/test/ng/directive/options.spec.js +3583 -0
- package/test/ng/directive/ref.spec.js +575 -0
- package/test/ng/directive/repeat.spec.js +1675 -0
- package/test/ng/directive/script.spec.js +52 -0
- package/test/ng/directive/scrset.spec.js +67 -0
- package/test/ng/directive/select.spec.js +2541 -0
- package/test/ng/directive/show-hide.spec.js +253 -0
- package/test/ng/directive/src.spec.js +157 -0
- package/test/ng/directive/style.spec.js +178 -0
- package/test/ng/directive/switch.spec.js +647 -0
- package/test/ng/directive/validators.spec.js +717 -0
- package/test/ng/document.spec.js +52 -0
- package/test/ng/filter/filter.spec.js +714 -0
- package/test/ng/filter/filters.spec.js +35 -0
- package/test/ng/filter/limit-to.spec.js +251 -0
- package/test/ng/filter/order-by.spec.js +891 -0
- package/test/ng/filter.spec.js +149 -0
- package/test/ng/http-backend.spec.js +398 -0
- package/test/ng/http.spec.js +4071 -0
- package/test/ng/interpolate.spec.js +642 -0
- package/test/ng/interval.spec.js +343 -0
- package/test/ng/location.spec.js +3488 -0
- package/test/ng/on.spec.js +229 -0
- package/test/ng/parse.spec.js +4655 -0
- package/test/ng/prop.spec.js +805 -0
- package/test/ng/q.spec.js +2904 -0
- package/test/ng/root-element.spec.js +16 -0
- package/test/ng/sanitize-uri.spec.js +249 -0
- package/test/ng/sce.spec.js +660 -0
- package/test/ng/scope.spec.js +3442 -0
- package/test/ng/template-request.spec.js +236 -0
- package/test/ng/timeout.spec.js +351 -0
- package/test/ng/url-utils.spec.js +156 -0
- package/test/ng/utils.spec.js +144 -0
- package/test/original-test.html +21 -0
- package/test/public.spec.js +34 -0
- package/test/sanitize/bing-html.spec.js +36 -0
- package/test/server/express.js +158 -0
- package/test/test-utils.js +11 -0
- package/tsconfig.json +17 -0
- package/types/angular.d.ts +138 -0
- package/types/global.d.ts +9 -0
- package/types/index.d.ts +2357 -0
- package/types/jqlite.d.ts +558 -0
- package/vite.config.js +14 -0
|
@@ -0,0 +1,1675 @@
|
|
|
1
|
+
import { publishExternalAPI } from "../../../src/public";
|
|
2
|
+
import { createInjector } from "../../../src/injector";
|
|
3
|
+
import { dealoc, jqLite } from "../../../src/jqLite";
|
|
4
|
+
import { forEach, valueFn } from "../../../src/core/utils";
|
|
5
|
+
import { Angular } from "../../../src/loader";
|
|
6
|
+
|
|
7
|
+
describe("ngRepeat", () => {
|
|
8
|
+
let element;
|
|
9
|
+
let $compile;
|
|
10
|
+
let scope;
|
|
11
|
+
let $exceptionHandler;
|
|
12
|
+
let $compileProvider;
|
|
13
|
+
let $templateCache;
|
|
14
|
+
let injector;
|
|
15
|
+
let logs = [];
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
delete window.angular;
|
|
19
|
+
logs = [];
|
|
20
|
+
publishExternalAPI().decorator("$exceptionHandler", function () {
|
|
21
|
+
return (exception, cause) => {
|
|
22
|
+
logs.push(exception);
|
|
23
|
+
console.error(exception, cause);
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
injector = createInjector([
|
|
28
|
+
"ng",
|
|
29
|
+
(_$compileProvider_) => {
|
|
30
|
+
$compileProvider = _$compileProvider_;
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
$compile = injector.get("$compile");
|
|
34
|
+
$exceptionHandler = injector.get("$exceptionHandler");
|
|
35
|
+
scope = injector.get("$rootScope");
|
|
36
|
+
$templateCache = injector.get("$templateCache");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
// if ($exceptionHandler.errors.length) {
|
|
41
|
+
// dump(jasmine.getEnv().currentSpec.getFullName());
|
|
42
|
+
// dump("$exceptionHandler has errors");
|
|
43
|
+
// dump($exceptionHandler.errors);
|
|
44
|
+
// expect($exceptionHandler.errors).toBe([]);
|
|
45
|
+
// }
|
|
46
|
+
dealoc(element);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should iterate over an array of objects", () => {
|
|
50
|
+
element = $compile(
|
|
51
|
+
'<ul><li ng-repeat="item in items">{{item.name}};</li></ul>',
|
|
52
|
+
)(scope);
|
|
53
|
+
|
|
54
|
+
Array.prototype.extraProperty = "should be ignored";
|
|
55
|
+
// INIT
|
|
56
|
+
scope.items = [{ name: "misko" }, { name: "shyam" }];
|
|
57
|
+
scope.$digest();
|
|
58
|
+
expect(element.find("li").length).toEqual(2);
|
|
59
|
+
expect(element.text()).toEqual("misko;shyam;");
|
|
60
|
+
delete Array.prototype.extraProperty;
|
|
61
|
+
|
|
62
|
+
// GROW
|
|
63
|
+
scope.items.push({ name: "adam" });
|
|
64
|
+
scope.$digest();
|
|
65
|
+
expect(element.find("li").length).toEqual(3);
|
|
66
|
+
expect(element.text()).toEqual("misko;shyam;adam;");
|
|
67
|
+
|
|
68
|
+
// // SHRINK
|
|
69
|
+
scope.items.pop();
|
|
70
|
+
scope.items.shift();
|
|
71
|
+
scope.$digest();
|
|
72
|
+
expect(element.find("li").length).toEqual(1);
|
|
73
|
+
expect(element.text()).toEqual("shyam;");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should be possible to use one-time bindings on the collection", () => {
|
|
77
|
+
element = $compile(
|
|
78
|
+
"<ul>" + '<li ng-repeat="item in ::items">{{item.name}};</li>' + "</ul>",
|
|
79
|
+
)(scope);
|
|
80
|
+
|
|
81
|
+
scope.$digest();
|
|
82
|
+
|
|
83
|
+
scope.items = [{ name: "misko" }, { name: "shyam" }];
|
|
84
|
+
scope.$digest();
|
|
85
|
+
expect(element.find("li").length).toEqual(2);
|
|
86
|
+
expect(element.text()).toEqual("misko;shyam;");
|
|
87
|
+
scope.items.push({ name: "adam" });
|
|
88
|
+
scope.$digest();
|
|
89
|
+
expect(element.find("li").length).toEqual(2);
|
|
90
|
+
expect(element.text()).toEqual("misko;shyam;");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should be possible to use one-time bindings on the content", () => {
|
|
94
|
+
element = $compile(
|
|
95
|
+
"<ul>" + '<li ng-repeat="item in items">{{::item.name}};</li>' + "</ul>",
|
|
96
|
+
)(scope);
|
|
97
|
+
|
|
98
|
+
scope.$digest();
|
|
99
|
+
|
|
100
|
+
scope.items = [{ name: "misko" }, { name: "shyam" }];
|
|
101
|
+
scope.$digest();
|
|
102
|
+
expect(element.find("li").length).toEqual(2);
|
|
103
|
+
expect(element.text()).toEqual("misko;shyam;");
|
|
104
|
+
scope.items.push({ name: "adam" });
|
|
105
|
+
scope.$digest();
|
|
106
|
+
expect(element.find("li").length).toEqual(3);
|
|
107
|
+
expect(element.text()).toEqual("misko;shyam;adam;");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should iterate over an array-like object", () => {
|
|
111
|
+
element = $compile(
|
|
112
|
+
"<ul>" + '<li ng-repeat="item in items">{{item.name}};</li>' + "</ul>",
|
|
113
|
+
)(scope);
|
|
114
|
+
|
|
115
|
+
document.getElementById("dummy").innerHTML =
|
|
116
|
+
"<a class='test' name='x'>a</a>" +
|
|
117
|
+
"<a class='test' name='y'>b</a>" +
|
|
118
|
+
"<a class='test' name='x'>c</a>";
|
|
119
|
+
|
|
120
|
+
const htmlCollection = window.document.getElementsByClassName("test");
|
|
121
|
+
scope.items = htmlCollection;
|
|
122
|
+
scope.$digest();
|
|
123
|
+
expect(element.find("li").length).toEqual(3);
|
|
124
|
+
expect(element.text()).toEqual("x;y;x;");
|
|
125
|
+
|
|
126
|
+
// reset dummy
|
|
127
|
+
document.getElementById("dummy").innerHTML = "";
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should iterate over an array-like class", () => {
|
|
131
|
+
function Collection() {}
|
|
132
|
+
// eslint-disable-next-line no-array-constructor
|
|
133
|
+
Collection.prototype = new Array();
|
|
134
|
+
Collection.prototype.length = 0;
|
|
135
|
+
|
|
136
|
+
const collection = new Collection();
|
|
137
|
+
collection.push({ name: "x" });
|
|
138
|
+
collection.push({ name: "y" });
|
|
139
|
+
collection.push({ name: "z" });
|
|
140
|
+
|
|
141
|
+
element = $compile(
|
|
142
|
+
"<ul>" + '<li ng-repeat="item in items">{{item.name}};</li>' + "</ul>",
|
|
143
|
+
)(scope);
|
|
144
|
+
|
|
145
|
+
scope.items = collection;
|
|
146
|
+
scope.$digest();
|
|
147
|
+
expect(element.find("li").length).toEqual(3);
|
|
148
|
+
expect(element.text()).toEqual("x;y;z;");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should iterate over on object/map", () => {
|
|
152
|
+
element = $compile(
|
|
153
|
+
"<ul>" +
|
|
154
|
+
'<li ng-repeat="(key, value) in items">{{key}}:{{value}}|</li>' +
|
|
155
|
+
"</ul>",
|
|
156
|
+
)(scope);
|
|
157
|
+
scope.items = { misko: "swe", shyam: "set" };
|
|
158
|
+
scope.$digest();
|
|
159
|
+
expect(element.text()).toEqual("misko:swe|shyam:set|");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should iterate over on object/map where (key,value) contains whitespaces", () => {
|
|
163
|
+
element = $compile(
|
|
164
|
+
"<ul>" +
|
|
165
|
+
'<li ng-repeat="( key , value ) in items">{{key}}:{{value}}|</li>' +
|
|
166
|
+
"</ul>",
|
|
167
|
+
)(scope);
|
|
168
|
+
scope.items = { me: "swe", you: "set" };
|
|
169
|
+
scope.$digest();
|
|
170
|
+
expect(element.text()).toEqual("me:swe|you:set|");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should iterate over an object/map with identical values", () => {
|
|
174
|
+
element = $compile(
|
|
175
|
+
"<ul>" +
|
|
176
|
+
'<li ng-repeat="(key, value) in items">{{key}}:{{value}}|</li>' +
|
|
177
|
+
"</ul>",
|
|
178
|
+
)(scope);
|
|
179
|
+
scope.items = {
|
|
180
|
+
age: 20,
|
|
181
|
+
wealth: 20,
|
|
182
|
+
prodname: "Bingo",
|
|
183
|
+
dogname: "Bingo",
|
|
184
|
+
codename: "20",
|
|
185
|
+
};
|
|
186
|
+
scope.$digest();
|
|
187
|
+
expect(element.text()).toEqual(
|
|
188
|
+
"age:20|wealth:20|prodname:Bingo|dogname:Bingo|codename:20|",
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should iterate over on object created using `Object.create(null)`", () => {
|
|
193
|
+
element = $compile(
|
|
194
|
+
"<ul>" +
|
|
195
|
+
'<li ng-repeat="(key, value) in items">{{key}}:{{value}}|</li>' +
|
|
196
|
+
"</ul>",
|
|
197
|
+
)(scope);
|
|
198
|
+
|
|
199
|
+
const items = Object.create(null);
|
|
200
|
+
items.misko = "swe";
|
|
201
|
+
items.shyam = "set";
|
|
202
|
+
|
|
203
|
+
scope.items = items;
|
|
204
|
+
scope.$digest();
|
|
205
|
+
expect(element.text()).toEqual("misko:swe|shyam:set|");
|
|
206
|
+
|
|
207
|
+
delete items.shyam;
|
|
208
|
+
scope.$digest();
|
|
209
|
+
expect(element.text()).toEqual("misko:swe|");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("track by", () => {
|
|
213
|
+
it("should track using expression function", () => {
|
|
214
|
+
element = $compile(
|
|
215
|
+
"<ul>" +
|
|
216
|
+
'<li ng-repeat="item in items track by item.id">{{item.name}};</li>' +
|
|
217
|
+
"</ul>",
|
|
218
|
+
)(scope);
|
|
219
|
+
scope.items = [{ id: "misko" }, { id: "igor" }];
|
|
220
|
+
scope.$digest();
|
|
221
|
+
const li0 = element.find("li")[0];
|
|
222
|
+
const li1 = element.find("li")[1];
|
|
223
|
+
|
|
224
|
+
scope.items.push(scope.items.shift());
|
|
225
|
+
scope.$digest();
|
|
226
|
+
expect(element.find("li")[0]).toBe(li1);
|
|
227
|
+
expect(element.find("li")[1]).toBe(li0);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should track using build in $id function", () => {
|
|
231
|
+
element = $compile(
|
|
232
|
+
"<ul>" +
|
|
233
|
+
'<li ng-repeat="item in items track by $id(item)">{{item.name}};</li>' +
|
|
234
|
+
"</ul>",
|
|
235
|
+
)(scope);
|
|
236
|
+
scope.items = [{ name: "misko" }, { name: "igor" }];
|
|
237
|
+
scope.$digest();
|
|
238
|
+
const li0 = element.find("li")[0];
|
|
239
|
+
const li1 = element.find("li")[1];
|
|
240
|
+
|
|
241
|
+
scope.items.push(scope.items.shift());
|
|
242
|
+
scope.$digest();
|
|
243
|
+
expect(element.find("li")[0]).toBe(li1);
|
|
244
|
+
expect(element.find("li")[1]).toBe(li0);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should still filter when track is present", () => {
|
|
248
|
+
scope.isIgor = function (item) {
|
|
249
|
+
return item.name === "igor";
|
|
250
|
+
};
|
|
251
|
+
element = $compile(
|
|
252
|
+
"<ul>" +
|
|
253
|
+
'<li ng-repeat="item in items | filter:isIgor track by $id(item)">{{item.name}};</li>' +
|
|
254
|
+
"</ul>",
|
|
255
|
+
)(scope);
|
|
256
|
+
scope.items = [{ name: "igor" }, { name: "misko" }];
|
|
257
|
+
scope.$digest();
|
|
258
|
+
|
|
259
|
+
expect(element.find("li").text()).toBe("igor;");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should track using provided function when a filter is present", () => {
|
|
263
|
+
scope.newArray = function (items) {
|
|
264
|
+
const newArray = [];
|
|
265
|
+
forEach(items, (item) => {
|
|
266
|
+
newArray.push({
|
|
267
|
+
id: item.id,
|
|
268
|
+
name: item.name,
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
return newArray;
|
|
272
|
+
};
|
|
273
|
+
element = $compile(
|
|
274
|
+
"<ul><li ng-repeat='item in items | filter:newArray track by item.id'>{{item.name}};</li></ul>",
|
|
275
|
+
)(scope);
|
|
276
|
+
scope.items = [
|
|
277
|
+
{ id: 1, name: "igor" },
|
|
278
|
+
{ id: 2, name: "misko" },
|
|
279
|
+
];
|
|
280
|
+
scope.$digest();
|
|
281
|
+
expect(element.text()).toBe("igor;misko;");
|
|
282
|
+
|
|
283
|
+
const li0 = element.find("li")[0];
|
|
284
|
+
const li1 = element.find("li")[1];
|
|
285
|
+
|
|
286
|
+
scope.items.push(scope.items.shift());
|
|
287
|
+
scope.$digest();
|
|
288
|
+
expect(element.find("li")[0]).toBe(li1);
|
|
289
|
+
expect(element.find("li")[1]).toBe(li0);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should iterate over an array of primitives", () => {
|
|
293
|
+
element = $compile(
|
|
294
|
+
"<ul>" +
|
|
295
|
+
'<li ng-repeat="item in items track by $index">{{item}};</li>' +
|
|
296
|
+
"</ul>",
|
|
297
|
+
)(scope);
|
|
298
|
+
|
|
299
|
+
// eslint-disable-next-line no-extend-native
|
|
300
|
+
Array.prototype.extraProperty = "should be ignored";
|
|
301
|
+
// INIT
|
|
302
|
+
scope.items = [true, true, true];
|
|
303
|
+
scope.$digest();
|
|
304
|
+
expect(element.find("li").length).toEqual(3);
|
|
305
|
+
expect(element.text()).toEqual("true;true;true;");
|
|
306
|
+
delete Array.prototype.extraProperty;
|
|
307
|
+
|
|
308
|
+
scope.items = [false, true, true];
|
|
309
|
+
scope.$digest();
|
|
310
|
+
expect(element.find("li").length).toEqual(3);
|
|
311
|
+
expect(element.text()).toEqual("false;true;true;");
|
|
312
|
+
|
|
313
|
+
scope.items = [false, true, false];
|
|
314
|
+
scope.$digest();
|
|
315
|
+
expect(element.find("li").length).toEqual(3);
|
|
316
|
+
expect(element.text()).toEqual("false;true;false;");
|
|
317
|
+
|
|
318
|
+
scope.items = [true];
|
|
319
|
+
scope.$digest();
|
|
320
|
+
expect(element.find("li").length).toEqual(1);
|
|
321
|
+
expect(element.text()).toEqual("true;");
|
|
322
|
+
|
|
323
|
+
scope.items = [true, true, false];
|
|
324
|
+
scope.$digest();
|
|
325
|
+
expect(element.find("li").length).toEqual(3);
|
|
326
|
+
expect(element.text()).toEqual("true;true;false;");
|
|
327
|
+
|
|
328
|
+
scope.items = [true, false, false];
|
|
329
|
+
scope.$digest();
|
|
330
|
+
expect(element.find("li").length).toEqual(3);
|
|
331
|
+
expect(element.text()).toEqual("true;false;false;");
|
|
332
|
+
|
|
333
|
+
// string
|
|
334
|
+
scope.items = ["a", "a", "a"];
|
|
335
|
+
scope.$digest();
|
|
336
|
+
expect(element.find("li").length).toEqual(3);
|
|
337
|
+
expect(element.text()).toEqual("a;a;a;");
|
|
338
|
+
|
|
339
|
+
scope.items = ["ab", "a", "a"];
|
|
340
|
+
scope.$digest();
|
|
341
|
+
expect(element.find("li").length).toEqual(3);
|
|
342
|
+
expect(element.text()).toEqual("ab;a;a;");
|
|
343
|
+
|
|
344
|
+
scope.items = ["test"];
|
|
345
|
+
scope.$digest();
|
|
346
|
+
expect(element.find("li").length).toEqual(1);
|
|
347
|
+
expect(element.text()).toEqual("test;");
|
|
348
|
+
|
|
349
|
+
scope.items = ["same", "value"];
|
|
350
|
+
scope.$digest();
|
|
351
|
+
expect(element.find("li").length).toEqual(2);
|
|
352
|
+
expect(element.text()).toEqual("same;value;");
|
|
353
|
+
|
|
354
|
+
// number
|
|
355
|
+
scope.items = [12, 12, 12];
|
|
356
|
+
scope.$digest();
|
|
357
|
+
expect(element.find("li").length).toEqual(3);
|
|
358
|
+
expect(element.text()).toEqual("12;12;12;");
|
|
359
|
+
|
|
360
|
+
scope.items = [53, 12, 27];
|
|
361
|
+
scope.$digest();
|
|
362
|
+
expect(element.find("li").length).toEqual(3);
|
|
363
|
+
expect(element.text()).toEqual("53;12;27;");
|
|
364
|
+
|
|
365
|
+
scope.items = [89];
|
|
366
|
+
scope.$digest();
|
|
367
|
+
expect(element.find("li").length).toEqual(1);
|
|
368
|
+
expect(element.text()).toEqual("89;");
|
|
369
|
+
|
|
370
|
+
scope.items = [89, 23];
|
|
371
|
+
scope.$digest();
|
|
372
|
+
expect(element.find("li").length).toEqual(2);
|
|
373
|
+
expect(element.text()).toEqual("89;23;");
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("should iterate over object with changing primitive property values", () => {
|
|
377
|
+
element = $compile(
|
|
378
|
+
"<ul>" +
|
|
379
|
+
'<li ng-repeat="(key, value) in items track by $index">' +
|
|
380
|
+
"{{key}}:{{value}};" +
|
|
381
|
+
'<input type="checkbox" ng-model="items[key]">' +
|
|
382
|
+
"</li>" +
|
|
383
|
+
"</ul>",
|
|
384
|
+
)(scope);
|
|
385
|
+
window.document.getElementById("dummy").appendChild(element[0]);
|
|
386
|
+
scope.items = { misko: true, shyam: true, zhenbo: true };
|
|
387
|
+
scope.$digest();
|
|
388
|
+
expect(element.find("li").length).toEqual(3);
|
|
389
|
+
expect(element.text()).toEqual("misko:true;shyam:true;zhenbo:true;");
|
|
390
|
+
element.find("input").eq(0)[0].click();
|
|
391
|
+
|
|
392
|
+
expect(element.text()).toEqual("misko:false;shyam:true;zhenbo:true;");
|
|
393
|
+
expect(element.find("input")[0].checked).toBe(false);
|
|
394
|
+
expect(element.find("input")[1].checked).toBe(true);
|
|
395
|
+
expect(element.find("input")[2].checked).toBe(true);
|
|
396
|
+
|
|
397
|
+
element.find("input").eq(0)[0].click();
|
|
398
|
+
expect(element.text()).toEqual("misko:true;shyam:true;zhenbo:true;");
|
|
399
|
+
expect(element.find("input")[0].checked).toBe(true);
|
|
400
|
+
expect(element.find("input")[1].checked).toBe(true);
|
|
401
|
+
expect(element.find("input")[2].checked).toBe(true);
|
|
402
|
+
|
|
403
|
+
element.find("input").eq(1)[0].click();
|
|
404
|
+
expect(element.text()).toEqual("misko:true;shyam:false;zhenbo:true;");
|
|
405
|
+
expect(element.find("input")[0].checked).toBe(true);
|
|
406
|
+
expect(element.find("input")[1].checked).toBe(false);
|
|
407
|
+
expect(element.find("input")[2].checked).toBe(true);
|
|
408
|
+
|
|
409
|
+
scope.items = { misko: false, shyam: true, zhenbo: true };
|
|
410
|
+
scope.$digest();
|
|
411
|
+
expect(element.text()).toEqual("misko:false;shyam:true;zhenbo:true;");
|
|
412
|
+
expect(element.find("input")[0].checked).toBe(false);
|
|
413
|
+
expect(element.find("input")[1].checked).toBe(true);
|
|
414
|
+
expect(element.find("input")[2].checked).toBe(true);
|
|
415
|
+
|
|
416
|
+
window.document.getElementById("dummy").innerHTML = "";
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("should invoke track by with correct locals", () => {
|
|
420
|
+
scope.trackBy = jasmine
|
|
421
|
+
.createSpy()
|
|
422
|
+
.and.callFake((k, v) => [k, v].join(""));
|
|
423
|
+
|
|
424
|
+
element = $compile(
|
|
425
|
+
"<ul>" +
|
|
426
|
+
'<li ng-repeat="(k, v) in [1, 2] track by trackBy(k, v)"></li>' +
|
|
427
|
+
"</ul>",
|
|
428
|
+
)(scope);
|
|
429
|
+
scope.$digest();
|
|
430
|
+
|
|
431
|
+
expect(scope.trackBy).toHaveBeenCalledTimes(2);
|
|
432
|
+
expect(scope.trackBy.calls.argsFor(0)).toEqual([0, 1]);
|
|
433
|
+
expect(scope.trackBy.calls.argsFor(1)).toEqual([1, 2]);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// https://github.com/angular/angular.js/issues/16776
|
|
437
|
+
it("should invoke nested track by with correct locals", () => {
|
|
438
|
+
scope.trackBy = jasmine
|
|
439
|
+
.createSpy()
|
|
440
|
+
.and.callFake((k1, v1, k2, v2) => [k1, v1, k2, v2].join(""));
|
|
441
|
+
|
|
442
|
+
element = $compile(
|
|
443
|
+
"<ul>" +
|
|
444
|
+
'<li ng-repeat="(k1, v1) in [1, 2]">' +
|
|
445
|
+
'<div ng-repeat="(k2, v2) in [3, 4] track by trackBy(k1, v1, k2, v2)"></div>' +
|
|
446
|
+
"</li>" +
|
|
447
|
+
"</ul>",
|
|
448
|
+
)(scope);
|
|
449
|
+
scope.$digest();
|
|
450
|
+
|
|
451
|
+
expect(scope.trackBy).toHaveBeenCalledTimes(4);
|
|
452
|
+
expect(scope.trackBy.calls.argsFor(0)).toEqual([0, 1, 0, 3]);
|
|
453
|
+
expect(scope.trackBy.calls.argsFor(1)).toEqual([0, 1, 1, 4]);
|
|
454
|
+
expect(scope.trackBy.calls.argsFor(2)).toEqual([1, 2, 0, 3]);
|
|
455
|
+
expect(scope.trackBy.calls.argsFor(3)).toEqual([1, 2, 1, 4]);
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
describe("alias as", () => {
|
|
460
|
+
it("should assigned the filtered to the target scope property if an alias is provided", () => {
|
|
461
|
+
element = $compile(
|
|
462
|
+
'<div ng-repeat="item in items | filter:x as results track by $index">{{item.name}}/</div>',
|
|
463
|
+
)(scope);
|
|
464
|
+
|
|
465
|
+
scope.items = [
|
|
466
|
+
{ name: "red" },
|
|
467
|
+
{ name: "blue" },
|
|
468
|
+
{ name: "green" },
|
|
469
|
+
{ name: "black" },
|
|
470
|
+
{ name: "orange" },
|
|
471
|
+
{ name: "blonde" },
|
|
472
|
+
];
|
|
473
|
+
|
|
474
|
+
expect(scope.results).toBeUndefined();
|
|
475
|
+
scope.$digest();
|
|
476
|
+
|
|
477
|
+
scope.x = "bl";
|
|
478
|
+
scope.$digest();
|
|
479
|
+
|
|
480
|
+
expect(scope.results).toEqual([
|
|
481
|
+
{ name: "blue" },
|
|
482
|
+
{ name: "black" },
|
|
483
|
+
{ name: "blonde" },
|
|
484
|
+
]);
|
|
485
|
+
|
|
486
|
+
scope.items = [];
|
|
487
|
+
scope.$digest();
|
|
488
|
+
|
|
489
|
+
expect(scope.results).toEqual([]);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("should render a message when the repeat list is empty", () => {
|
|
493
|
+
element = $compile(
|
|
494
|
+
"<div>" +
|
|
495
|
+
' <div ng-repeat="item in items | filter:x as results">{{item}}</div>' +
|
|
496
|
+
' <div ng-if="results.length === 0">' +
|
|
497
|
+
" No results found..." +
|
|
498
|
+
" </div>" +
|
|
499
|
+
"</div>",
|
|
500
|
+
)(scope);
|
|
501
|
+
|
|
502
|
+
scope.items = [1, 2, 3, 4, 5, 6];
|
|
503
|
+
scope.$digest();
|
|
504
|
+
expect(element.text().trim()).toEqual("123456");
|
|
505
|
+
|
|
506
|
+
scope.x = "0";
|
|
507
|
+
scope.$digest();
|
|
508
|
+
|
|
509
|
+
expect(element.text().trim()).toEqual("No results found...");
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("should support alias identifiers containing reserved words", () => {
|
|
513
|
+
scope.x = "bl";
|
|
514
|
+
scope.items = [
|
|
515
|
+
{ name: "red" },
|
|
516
|
+
{ name: "blue" },
|
|
517
|
+
{ name: "green" },
|
|
518
|
+
{ name: "black" },
|
|
519
|
+
{ name: "orange" },
|
|
520
|
+
{ name: "blonde" },
|
|
521
|
+
];
|
|
522
|
+
forEach(
|
|
523
|
+
["null2", "qthis", "qthisq", "fundefined", "$$parent"],
|
|
524
|
+
(name) => {
|
|
525
|
+
const expr = `item in items | filter:x as ${name} track by $index`;
|
|
526
|
+
element = $compile(`<div><div ng-repeat="${expr}"></div></div>`)(
|
|
527
|
+
scope,
|
|
528
|
+
);
|
|
529
|
+
scope.$digest();
|
|
530
|
+
expect(scope[name]).toEqual([
|
|
531
|
+
{ name: "blue" },
|
|
532
|
+
{ name: "black" },
|
|
533
|
+
{ name: "blonde" },
|
|
534
|
+
]);
|
|
535
|
+
dealoc(element);
|
|
536
|
+
},
|
|
537
|
+
);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("should throw if alias identifier is not a simple identifier", () => {
|
|
541
|
+
scope.x = "bl";
|
|
542
|
+
scope.items = [
|
|
543
|
+
{ name: "red" },
|
|
544
|
+
{ name: "blue" },
|
|
545
|
+
{ name: "green" },
|
|
546
|
+
{ name: "black" },
|
|
547
|
+
{ name: "orange" },
|
|
548
|
+
{ name: "blonde" },
|
|
549
|
+
];
|
|
550
|
+
|
|
551
|
+
forEach(
|
|
552
|
+
[
|
|
553
|
+
"null",
|
|
554
|
+
"this",
|
|
555
|
+
"undefined",
|
|
556
|
+
"$parent",
|
|
557
|
+
"$root",
|
|
558
|
+
"$id",
|
|
559
|
+
"$index",
|
|
560
|
+
"$first",
|
|
561
|
+
"$middle",
|
|
562
|
+
"$last",
|
|
563
|
+
"$even",
|
|
564
|
+
"$odd",
|
|
565
|
+
"obj[key]",
|
|
566
|
+
'obj["key"]',
|
|
567
|
+
"obj['key']",
|
|
568
|
+
"obj.property",
|
|
569
|
+
"foo=6",
|
|
570
|
+
],
|
|
571
|
+
(expr) => {
|
|
572
|
+
const expression =
|
|
573
|
+
`item in items | filter:x as ${expr} track by $index`.replace(
|
|
574
|
+
/"/g,
|
|
575
|
+
""",
|
|
576
|
+
);
|
|
577
|
+
element = $compile(
|
|
578
|
+
`<div>` +
|
|
579
|
+
` <div ng-repeat="${expression}">{{item}}</div>` +
|
|
580
|
+
`</div>`,
|
|
581
|
+
)(scope);
|
|
582
|
+
expect(logs.shift().message).toMatch(/must be a valid JS identifier/);
|
|
583
|
+
|
|
584
|
+
dealoc(element);
|
|
585
|
+
},
|
|
586
|
+
);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it("should allow expressions over multiple lines", () => {
|
|
590
|
+
element = $compile(
|
|
591
|
+
"<ul>" +
|
|
592
|
+
'<li ng-repeat="item in items\n' +
|
|
593
|
+
'| filter:isTrue">{{item.name}}/</li>' +
|
|
594
|
+
"</ul>",
|
|
595
|
+
)(scope);
|
|
596
|
+
|
|
597
|
+
scope.isTrue = function () {
|
|
598
|
+
return true;
|
|
599
|
+
};
|
|
600
|
+
scope.items = [{ name: "igor" }, { name: "misko" }];
|
|
601
|
+
|
|
602
|
+
scope.$digest();
|
|
603
|
+
|
|
604
|
+
expect(element.text()).toEqual("igor/misko/");
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it("should strip white space characters correctly", () => {
|
|
608
|
+
element = $compile(
|
|
609
|
+
"<ul>" +
|
|
610
|
+
'<li ng-repeat="item \t\n \t in \n \t\n\n \nitems \t\t\n | filter:\n\n{' +
|
|
611
|
+
"\n\t name:\n\n 'ko'\n\n}\n\n | orderBy: \t \n 'name' \n\n" +
|
|
612
|
+
'track \t\n by \n\n\t $index \t\n ">{{item.name}}/</li>' +
|
|
613
|
+
"</ul>",
|
|
614
|
+
)(scope);
|
|
615
|
+
|
|
616
|
+
scope.items = [{ name: "igor" }, { name: "misko" }];
|
|
617
|
+
|
|
618
|
+
scope.$digest();
|
|
619
|
+
|
|
620
|
+
expect(element.text()).toEqual("misko/");
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it("should not ngRepeat over parent properties", () => {
|
|
624
|
+
const Class = function () {};
|
|
625
|
+
Class.prototype.abc = function () {};
|
|
626
|
+
Class.prototype.value = "abc";
|
|
627
|
+
|
|
628
|
+
element = $compile(
|
|
629
|
+
"<ul>" +
|
|
630
|
+
'<li ng-repeat="(key, value) in items">{{key}}:{{value}};</li>' +
|
|
631
|
+
"</ul>",
|
|
632
|
+
)(scope);
|
|
633
|
+
scope.items = new Class();
|
|
634
|
+
scope.items.name = "value";
|
|
635
|
+
scope.$digest();
|
|
636
|
+
expect(element.text()).toEqual("name:value;");
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it("should error on wrong parsing of ngRepeat", () => {
|
|
640
|
+
element = jqLite('<ul><li ng-repeat="i dont parse"></li></ul>');
|
|
641
|
+
$compile(element)(scope);
|
|
642
|
+
expect(logs.shift().message).toMatch(/i dont parse/);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it("should throw error when left-hand-side of ngRepeat can't be parsed", () => {
|
|
646
|
+
element = jqLite('<ul><li ng-repeat="i dont parse in foo"></li></ul>');
|
|
647
|
+
$compile(element)(scope);
|
|
648
|
+
expect(logs.shift().message).toMatch(/i dont parse/);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it("should expose iterator offset as $index when iterating over arrays", () => {
|
|
652
|
+
element = $compile(
|
|
653
|
+
"<ul>" +
|
|
654
|
+
'<li ng-repeat="item in items">{{item}}:{{$index}}|</li>' +
|
|
655
|
+
"</ul>",
|
|
656
|
+
)(scope);
|
|
657
|
+
scope.items = ["misko", "shyam", "frodo"];
|
|
658
|
+
scope.$digest();
|
|
659
|
+
expect(element.text()).toEqual("misko:0|shyam:1|frodo:2|");
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it("should expose iterator offset as $index when iterating over objects", () => {
|
|
663
|
+
element = $compile(
|
|
664
|
+
"<ul>" +
|
|
665
|
+
'<li ng-repeat="(key, val) in items">{{key}}:{{val}}:{{$index}}|</li>' +
|
|
666
|
+
"</ul>",
|
|
667
|
+
)(scope);
|
|
668
|
+
scope.items = { misko: "m", shyam: "s", frodo: "f" };
|
|
669
|
+
scope.$digest();
|
|
670
|
+
expect(element.text()).toEqual("misko:m:0|shyam:s:1|frodo:f:2|");
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it("should expose iterator offset as $index when iterating over objects with length key value 0", () => {
|
|
674
|
+
element = $compile(
|
|
675
|
+
"<ul>" +
|
|
676
|
+
'<li ng-repeat="(key, val) in items">{{key}}:{{val}}:{{$index}}|</li>' +
|
|
677
|
+
"</ul>",
|
|
678
|
+
)(scope);
|
|
679
|
+
scope.items = { misko: "m", shyam: "s", frodo: "f", length: 0 };
|
|
680
|
+
scope.$digest();
|
|
681
|
+
expect(element.text()).toEqual(
|
|
682
|
+
"misko:m:0|shyam:s:1|frodo:f:2|length:0:3|",
|
|
683
|
+
);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it("should expose iterator position as $first, $middle and $last when iterating over arrays", () => {
|
|
687
|
+
element = $compile(
|
|
688
|
+
"<ul>" +
|
|
689
|
+
'<li ng-repeat="item in items">{{item}}:{{$first}}-{{$middle}}-{{$last}}|</li>' +
|
|
690
|
+
"</ul>",
|
|
691
|
+
)(scope);
|
|
692
|
+
scope.items = ["misko", "shyam", "doug"];
|
|
693
|
+
scope.$digest();
|
|
694
|
+
expect(element.text()).toEqual(
|
|
695
|
+
"misko:true-false-false|shyam:false-true-false|doug:false-false-true|",
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
scope.items.push("frodo");
|
|
699
|
+
scope.$digest();
|
|
700
|
+
expect(element.text()).toEqual(
|
|
701
|
+
"misko:true-false-false|" +
|
|
702
|
+
"shyam:false-true-false|" +
|
|
703
|
+
"doug:false-true-false|" +
|
|
704
|
+
"frodo:false-false-true|",
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
scope.items.pop();
|
|
708
|
+
scope.items.pop();
|
|
709
|
+
scope.$digest();
|
|
710
|
+
expect(element.text()).toEqual(
|
|
711
|
+
"misko:true-false-false|shyam:false-false-true|",
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
scope.items.pop();
|
|
715
|
+
scope.$digest();
|
|
716
|
+
expect(element.text()).toEqual("misko:true-false-true|");
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
it("should expose iterator position as $even and $odd when iterating over arrays", () => {
|
|
720
|
+
element = $compile(
|
|
721
|
+
"<ul>" +
|
|
722
|
+
'<li ng-repeat="item in items">{{item}}:{{$even}}-{{$odd}}|</li>' +
|
|
723
|
+
"</ul>",
|
|
724
|
+
)(scope);
|
|
725
|
+
scope.items = ["misko", "shyam", "doug"];
|
|
726
|
+
scope.$digest();
|
|
727
|
+
expect(element.text()).toEqual(
|
|
728
|
+
"misko:true-false|shyam:false-true|doug:true-false|",
|
|
729
|
+
);
|
|
730
|
+
|
|
731
|
+
scope.items.push("frodo");
|
|
732
|
+
scope.$digest();
|
|
733
|
+
expect(element.text()).toBe(
|
|
734
|
+
"misko:true-false|" +
|
|
735
|
+
"shyam:false-true|" +
|
|
736
|
+
"doug:true-false|" +
|
|
737
|
+
"frodo:false-true|",
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
scope.items.shift();
|
|
741
|
+
scope.items.pop();
|
|
742
|
+
scope.$digest();
|
|
743
|
+
expect(element.text()).toBe("shyam:true-false|doug:false-true|");
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
it("should expose iterator position as $first, $middle and $last when iterating over objects", () => {
|
|
747
|
+
element = $compile(
|
|
748
|
+
"<ul>" +
|
|
749
|
+
'<li ng-repeat="(key, val) in items">{{key}}:{{val}}:{{$first}}-{{$middle}}-{{$last}}|</li>' +
|
|
750
|
+
"</ul>",
|
|
751
|
+
)(scope);
|
|
752
|
+
scope.items = { misko: "m", shyam: "s", doug: "d", frodo: "f" };
|
|
753
|
+
scope.$digest();
|
|
754
|
+
expect(element.text()).toEqual(
|
|
755
|
+
"misko:m:true-false-false|" +
|
|
756
|
+
"shyam:s:false-true-false|" +
|
|
757
|
+
"doug:d:false-true-false|" +
|
|
758
|
+
"frodo:f:false-false-true|",
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
delete scope.items.doug;
|
|
762
|
+
delete scope.items.frodo;
|
|
763
|
+
scope.$digest();
|
|
764
|
+
expect(element.text()).toEqual(
|
|
765
|
+
"misko:m:true-false-false|shyam:s:false-false-true|",
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
delete scope.items.shyam;
|
|
769
|
+
scope.$digest();
|
|
770
|
+
expect(element.text()).toEqual("misko:m:true-false-true|");
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it("should expose iterator position as $even and $odd when iterating over objects", () => {
|
|
774
|
+
element = $compile(
|
|
775
|
+
"<ul>" +
|
|
776
|
+
'<li ng-repeat="(key, val) in items">{{key}}:{{val}}:{{$even}}-{{$odd}}|</li>' +
|
|
777
|
+
"</ul>",
|
|
778
|
+
)(scope);
|
|
779
|
+
scope.items = { misko: "m", shyam: "s", doug: "d", frodo: "f" };
|
|
780
|
+
scope.$digest();
|
|
781
|
+
expect(element.text()).toBe(
|
|
782
|
+
"misko:m:true-false|" +
|
|
783
|
+
"shyam:s:false-true|" +
|
|
784
|
+
"doug:d:true-false|" +
|
|
785
|
+
"frodo:f:false-true|",
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
delete scope.items.frodo;
|
|
789
|
+
delete scope.items.shyam;
|
|
790
|
+
scope.$digest();
|
|
791
|
+
expect(element.text()).toBe("misko:m:true-false|doug:d:false-true|");
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
it("should calculate $first, $middle and $last when we filter out properties from an obj", () => {
|
|
795
|
+
element = $compile(
|
|
796
|
+
"<ul>" +
|
|
797
|
+
'<li ng-repeat="(key, val) in items">{{key}}:{{val}}:{{$first}}-{{$middle}}-{{$last}}|</li>' +
|
|
798
|
+
"</ul>",
|
|
799
|
+
)(scope);
|
|
800
|
+
scope.items = {
|
|
801
|
+
misko: "m",
|
|
802
|
+
shyam: "s",
|
|
803
|
+
doug: "d",
|
|
804
|
+
frodo: "f",
|
|
805
|
+
$toBeFilteredOut: "xxxx",
|
|
806
|
+
};
|
|
807
|
+
scope.$digest();
|
|
808
|
+
|
|
809
|
+
expect(element.text()).toEqual(
|
|
810
|
+
"misko:m:true-false-false|" +
|
|
811
|
+
"shyam:s:false-true-false|" +
|
|
812
|
+
"doug:d:false-true-false|" +
|
|
813
|
+
"frodo:f:false-false-true|",
|
|
814
|
+
);
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it("should calculate $even and $odd when we filter out properties from an obj", () => {
|
|
818
|
+
element = $compile(
|
|
819
|
+
"<ul>" +
|
|
820
|
+
'<li ng-repeat="(key, val) in items">{{key}}:{{val}}:{{$even}}-{{$odd}}|</li>' +
|
|
821
|
+
"</ul>",
|
|
822
|
+
)(scope);
|
|
823
|
+
scope.items = {
|
|
824
|
+
misko: "m",
|
|
825
|
+
shyam: "s",
|
|
826
|
+
doug: "d",
|
|
827
|
+
frodo: "f",
|
|
828
|
+
$toBeFilteredOut: "xxxx",
|
|
829
|
+
};
|
|
830
|
+
scope.$digest();
|
|
831
|
+
expect(element.text()).toEqual(
|
|
832
|
+
"misko:m:true-false|" +
|
|
833
|
+
"shyam:s:false-true|" +
|
|
834
|
+
"doug:d:true-false|" +
|
|
835
|
+
"frodo:f:false-true|",
|
|
836
|
+
);
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
it("should ignore $ and $$ properties", () => {
|
|
840
|
+
element = $compile('<ul><li ng-repeat="i in items">{{i}}|</li></ul>')(
|
|
841
|
+
scope,
|
|
842
|
+
);
|
|
843
|
+
scope.items = ["a", "b", "c"];
|
|
844
|
+
scope.items.$$hashKey = "xxx";
|
|
845
|
+
scope.items.$root = "yyy";
|
|
846
|
+
scope.$digest();
|
|
847
|
+
|
|
848
|
+
expect(element.text()).toEqual("a|b|c|");
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it("should repeat over nested arrays", () => {
|
|
852
|
+
element = $compile(
|
|
853
|
+
"<ul>" +
|
|
854
|
+
'<li ng-repeat="subgroup in groups">' +
|
|
855
|
+
'<div ng-repeat="group in subgroup">{{group}}|</div>X' +
|
|
856
|
+
"</li>" +
|
|
857
|
+
"</ul>",
|
|
858
|
+
)(scope);
|
|
859
|
+
scope.groups = [
|
|
860
|
+
["a", "b"],
|
|
861
|
+
["c", "d"],
|
|
862
|
+
];
|
|
863
|
+
scope.$digest();
|
|
864
|
+
|
|
865
|
+
expect(element.text()).toEqual("a|b|Xc|d|X");
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it("should ignore non-array element properties when iterating over an array", () => {
|
|
869
|
+
element = $compile(
|
|
870
|
+
'<ul><li ng-repeat="item in array">{{item}}|</li></ul>',
|
|
871
|
+
)(scope);
|
|
872
|
+
scope.array = ["a", "b", "c"];
|
|
873
|
+
scope.array.foo = "23";
|
|
874
|
+
scope.array.bar = function () {};
|
|
875
|
+
scope.$digest();
|
|
876
|
+
|
|
877
|
+
expect(element.text()).toBe("a|b|c|");
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
it("should iterate over non-existent elements of a sparse array", () => {
|
|
881
|
+
element = $compile(
|
|
882
|
+
'<ul><li ng-repeat="item in array track by $index">{{item}}|</li></ul>',
|
|
883
|
+
)(scope);
|
|
884
|
+
scope.array = ["a", "b"];
|
|
885
|
+
scope.array[4] = "c";
|
|
886
|
+
scope.array[6] = "d";
|
|
887
|
+
scope.$digest();
|
|
888
|
+
|
|
889
|
+
expect(element.text()).toBe("a|b|||c||d|");
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
it("should iterate over all kinds of types", () => {
|
|
893
|
+
element = $compile(
|
|
894
|
+
'<ul><li ng-repeat="item in array">{{item}}|</li></ul>',
|
|
895
|
+
)(scope);
|
|
896
|
+
scope.array = ["a", 1, null, undefined, {}];
|
|
897
|
+
scope.$digest();
|
|
898
|
+
|
|
899
|
+
expect(element.text()).toMatch(/a\|1\|\|\|\{\s*\}\|/);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
it("should preserve data on move of elements", () => {
|
|
903
|
+
element = $compile(
|
|
904
|
+
'<ul><li ng-repeat="item in array">{{item}}|</li></ul>',
|
|
905
|
+
)(scope);
|
|
906
|
+
scope.array = ["a", "b"];
|
|
907
|
+
scope.$digest();
|
|
908
|
+
|
|
909
|
+
let lis = element.find("li");
|
|
910
|
+
lis.eq(0).data("mark", "a");
|
|
911
|
+
lis.eq(1).data("mark", "b");
|
|
912
|
+
|
|
913
|
+
scope.array = ["b", "a"];
|
|
914
|
+
scope.$digest();
|
|
915
|
+
|
|
916
|
+
lis = element.find("li");
|
|
917
|
+
expect(lis.eq(0).data("mark")).toEqual("b");
|
|
918
|
+
expect(lis.eq(1).data("mark")).toEqual("a");
|
|
919
|
+
});
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
describe("nesting in replaced directive templates", () => {
|
|
923
|
+
it("should work when placed on a non-root element of attr directive with SYNC replaced template", () => {
|
|
924
|
+
$compileProvider.directive("rr", () => ({
|
|
925
|
+
restrict: "A",
|
|
926
|
+
replace: true,
|
|
927
|
+
template: '<div ng-repeat="i in items">{{i}}|</div>',
|
|
928
|
+
}));
|
|
929
|
+
element = jqLite("<div><span rr>{{i}}|</span></div>");
|
|
930
|
+
$compile(element)(scope);
|
|
931
|
+
scope.$apply();
|
|
932
|
+
expect(element.text()).toBe("");
|
|
933
|
+
|
|
934
|
+
scope.items = [1, 2];
|
|
935
|
+
scope.$apply();
|
|
936
|
+
expect(element.text()).toBe("1|2|");
|
|
937
|
+
|
|
938
|
+
expect(element[0].children[0].outerHTML).toBe(
|
|
939
|
+
'<div ng-repeat="i in items" rr="">1|</div>',
|
|
940
|
+
);
|
|
941
|
+
expect(element[0].children[1].outerHTML).toBe(
|
|
942
|
+
'<div ng-repeat="i in items" rr="">2|</div>',
|
|
943
|
+
);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
it("should work when placed on a non-root element of attr directive with ASYNC replaced template", () => {
|
|
947
|
+
$compileProvider.directive("rr", () => ({
|
|
948
|
+
restrict: "A",
|
|
949
|
+
replace: true,
|
|
950
|
+
templateUrl: "rr.html",
|
|
951
|
+
}));
|
|
952
|
+
|
|
953
|
+
$templateCache.put("rr.html", '<div ng-repeat="i in items">{{i}}|</div>');
|
|
954
|
+
|
|
955
|
+
element = jqLite("<div><span rr>{{i}}|</span></div>");
|
|
956
|
+
$compile(element)(scope);
|
|
957
|
+
scope.$apply();
|
|
958
|
+
expect(element.text()).toBe("");
|
|
959
|
+
|
|
960
|
+
scope.items = [1, 2];
|
|
961
|
+
scope.$apply();
|
|
962
|
+
expect(element.text()).toBe("1|2|");
|
|
963
|
+
expect(element[0].children[0].outerHTML).toBe(
|
|
964
|
+
'<div ng-repeat="i in items" rr="">1|</div>',
|
|
965
|
+
);
|
|
966
|
+
expect(element[0].children[1].outerHTML).toBe(
|
|
967
|
+
'<div ng-repeat="i in items" rr="">2|</div>',
|
|
968
|
+
);
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
it("should work when placed on a root element of attr directive with SYNC replaced template", () => {
|
|
972
|
+
$compileProvider.directive("replaceMeWithRepeater", () => ({
|
|
973
|
+
replace: true,
|
|
974
|
+
template: '<span ng-repeat="i in items">{{log(i)}}</span>',
|
|
975
|
+
}));
|
|
976
|
+
element = jqLite("<span replace-me-with-repeater></span>");
|
|
977
|
+
$compile(element)(scope);
|
|
978
|
+
expect(element.text()).toBe("");
|
|
979
|
+
const scopeLog = [];
|
|
980
|
+
scope.log = function (t) {
|
|
981
|
+
scopeLog.push(t);
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
// This creates one item, but it has no parent so we can't get to it
|
|
985
|
+
scope.items = [1, 2];
|
|
986
|
+
scope.$apply();
|
|
987
|
+
expect(scopeLog).toContain(1);
|
|
988
|
+
expect(scopeLog).toContain(2);
|
|
989
|
+
scopeLog.length = 0;
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
it("should work when placed on a root element of attr directive with ASYNC replaced template", () => {
|
|
993
|
+
$compileProvider.directive("replaceMeWithRepeater", () => ({
|
|
994
|
+
replace: true,
|
|
995
|
+
templateUrl: "replace-me-with-repeater.html",
|
|
996
|
+
}));
|
|
997
|
+
$templateCache.put(
|
|
998
|
+
"replace-me-with-repeater.html",
|
|
999
|
+
'<div ng-repeat="i in items">{{log(i)}}</div>',
|
|
1000
|
+
);
|
|
1001
|
+
element = jqLite(
|
|
1002
|
+
"<span>-</span><span replace-me-with-repeater></span><span>-</span>",
|
|
1003
|
+
);
|
|
1004
|
+
$compile(element)(scope);
|
|
1005
|
+
expect(element.text()).toBe("--");
|
|
1006
|
+
const logs = [];
|
|
1007
|
+
scope.log = function (t) {
|
|
1008
|
+
logs.push(t);
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
// This creates one item, but it has no parent so we can't get to it
|
|
1012
|
+
scope.items = [1, 2];
|
|
1013
|
+
scope.$apply();
|
|
1014
|
+
expect(logs).toContain(1);
|
|
1015
|
+
expect(logs).toContain(2);
|
|
1016
|
+
logs.length = 0;
|
|
1017
|
+
|
|
1018
|
+
// This cleans up to prevent memory leak
|
|
1019
|
+
scope.items = [];
|
|
1020
|
+
scope.$apply();
|
|
1021
|
+
expect(element[0].outerHTML).toBe(`<span>-</span>`);
|
|
1022
|
+
expect(logs.length).toBe(0);
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
it("should work when placed on a root element of element directive with SYNC replaced template", () => {
|
|
1026
|
+
$compileProvider.directive("replaceMeWithRepeater", () => ({
|
|
1027
|
+
restrict: "E",
|
|
1028
|
+
replace: true,
|
|
1029
|
+
template: '<div ng-repeat="i in [1,2,3]">{{i}}</div>',
|
|
1030
|
+
}));
|
|
1031
|
+
element = $compile(
|
|
1032
|
+
"<div><replace-me-with-repeater></replace-me-with-repeater></div>",
|
|
1033
|
+
)(scope);
|
|
1034
|
+
expect(element.text()).toBe("");
|
|
1035
|
+
scope.$apply();
|
|
1036
|
+
expect(element.text()).toBe("123");
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
it("should work when placed on a root element of element directive with ASYNC replaced template", () => {
|
|
1040
|
+
$compileProvider.directive("replaceMeWithRepeater", () => ({
|
|
1041
|
+
restrict: "E",
|
|
1042
|
+
replace: true,
|
|
1043
|
+
templateUrl: "replace-me-with-repeater.html",
|
|
1044
|
+
}));
|
|
1045
|
+
$templateCache.put(
|
|
1046
|
+
"replace-me-with-repeater.html",
|
|
1047
|
+
'<div ng-repeat="i in [1,2,3]">{{i}}</div>',
|
|
1048
|
+
);
|
|
1049
|
+
element = $compile(
|
|
1050
|
+
"<div><replace-me-with-repeater></replace-me-with-repeater></div>",
|
|
1051
|
+
)(scope);
|
|
1052
|
+
expect(element.text()).toBe("");
|
|
1053
|
+
scope.$apply();
|
|
1054
|
+
expect(element.text()).toBe("123");
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
it("should work when combined with an ASYNC template that loads after the first digest", (done) => {
|
|
1058
|
+
$compileProvider.directive("test", () => ({
|
|
1059
|
+
templateUrl: "test.html",
|
|
1060
|
+
}));
|
|
1061
|
+
element = jqLite('<div><div ng-repeat="i in items" test></div></div>');
|
|
1062
|
+
$compile(element)(scope);
|
|
1063
|
+
scope.items = [1];
|
|
1064
|
+
scope.$apply();
|
|
1065
|
+
expect(element.text()).toBe("");
|
|
1066
|
+
|
|
1067
|
+
setTimeout(() => {
|
|
1068
|
+
expect(element.text()).toBe("hello");
|
|
1069
|
+
|
|
1070
|
+
scope.items = [];
|
|
1071
|
+
scope.$apply();
|
|
1072
|
+
// Note: there are still comments in element!
|
|
1073
|
+
expect(element.children().length).toBe(0);
|
|
1074
|
+
expect(element.text()).toBe("");
|
|
1075
|
+
done();
|
|
1076
|
+
}, 300);
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
it("should add separator comments after each item", () => {
|
|
1080
|
+
const check = function () {
|
|
1081
|
+
const children = element.find("div");
|
|
1082
|
+
expect(children.length).toBe(3);
|
|
1083
|
+
|
|
1084
|
+
// Note: COMMENT_NODE === 8
|
|
1085
|
+
expect(children[0].nextSibling.nodeType).toBe(8);
|
|
1086
|
+
expect(children[0].nextSibling.nodeValue).toBe(
|
|
1087
|
+
" end ngRepeat: val in values ",
|
|
1088
|
+
);
|
|
1089
|
+
expect(children[1].nextSibling.nodeType).toBe(8);
|
|
1090
|
+
expect(children[1].nextSibling.nodeValue).toBe(
|
|
1091
|
+
" end ngRepeat: val in values ",
|
|
1092
|
+
);
|
|
1093
|
+
expect(children[2].nextSibling.nodeType).toBe(8);
|
|
1094
|
+
expect(children[2].nextSibling.nodeValue).toBe(
|
|
1095
|
+
" end ngRepeat: val in values ",
|
|
1096
|
+
);
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
scope.values = [1, 2, 3];
|
|
1100
|
+
|
|
1101
|
+
element = $compile(
|
|
1102
|
+
"<div>" +
|
|
1103
|
+
'<div ng-repeat="val in values">val:{{val}};</div>' +
|
|
1104
|
+
"</div>",
|
|
1105
|
+
)(scope);
|
|
1106
|
+
|
|
1107
|
+
scope.$digest();
|
|
1108
|
+
check();
|
|
1109
|
+
|
|
1110
|
+
scope.values.shift();
|
|
1111
|
+
scope.values.push(4);
|
|
1112
|
+
scope.$digest();
|
|
1113
|
+
check();
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
it("should remove whole block even if the number of elements inside it changes", () => {
|
|
1117
|
+
scope.values = [1, 2, 3];
|
|
1118
|
+
|
|
1119
|
+
element = $compile(
|
|
1120
|
+
"<div>" +
|
|
1121
|
+
'<div ng-repeat-start="val in values"></div>' +
|
|
1122
|
+
"<span>{{val}}</span>" +
|
|
1123
|
+
"<p ng-repeat-end></p>" +
|
|
1124
|
+
"</div>",
|
|
1125
|
+
)(scope);
|
|
1126
|
+
|
|
1127
|
+
scope.$digest();
|
|
1128
|
+
|
|
1129
|
+
const ends = element.find("p");
|
|
1130
|
+
expect(ends.length).toBe(3);
|
|
1131
|
+
|
|
1132
|
+
// insert an extra element inside the second block
|
|
1133
|
+
const extra = jqLite("<strong></strong>")[0];
|
|
1134
|
+
element[0].insertBefore(extra, ends[1]);
|
|
1135
|
+
|
|
1136
|
+
scope.values.splice(1, 1);
|
|
1137
|
+
scope.$digest();
|
|
1138
|
+
|
|
1139
|
+
// expect the strong tag to be removed too
|
|
1140
|
+
expect(
|
|
1141
|
+
Array.from(element[0].children).map((x) => x.tagName.toLowerCase()),
|
|
1142
|
+
).toEqual(["div", "span", "p", "div", "span", "p"]);
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
it("should move whole block even if the number of elements inside it changes", () => {
|
|
1146
|
+
scope.values = [1, 2, 3];
|
|
1147
|
+
|
|
1148
|
+
element = $compile(
|
|
1149
|
+
"<div>" +
|
|
1150
|
+
'<div ng-repeat-start="val in values"></div>' +
|
|
1151
|
+
"<span>{{val}}</span>" +
|
|
1152
|
+
"<p ng-repeat-end></p>" +
|
|
1153
|
+
"</div>",
|
|
1154
|
+
)(scope);
|
|
1155
|
+
|
|
1156
|
+
scope.$digest();
|
|
1157
|
+
|
|
1158
|
+
const ends = element.find("p");
|
|
1159
|
+
expect(ends.length).toBe(3);
|
|
1160
|
+
|
|
1161
|
+
// insert an extra element inside the third block
|
|
1162
|
+
const extra = jqLite("<strong></strong>")[0];
|
|
1163
|
+
element[0].insertBefore(extra, ends[2]);
|
|
1164
|
+
|
|
1165
|
+
// move the third block to the beginning
|
|
1166
|
+
scope.values.unshift(scope.values.pop());
|
|
1167
|
+
scope.$digest();
|
|
1168
|
+
|
|
1169
|
+
// expect the strong tag to be moved too
|
|
1170
|
+
expect(
|
|
1171
|
+
Array.from(element[0].children).map((x) => x.tagName.toLowerCase()),
|
|
1172
|
+
).toEqual([
|
|
1173
|
+
"div",
|
|
1174
|
+
"span",
|
|
1175
|
+
"strong",
|
|
1176
|
+
"p",
|
|
1177
|
+
"div",
|
|
1178
|
+
"span",
|
|
1179
|
+
"p",
|
|
1180
|
+
"div",
|
|
1181
|
+
"span",
|
|
1182
|
+
"p",
|
|
1183
|
+
]);
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
describe("stability", () => {
|
|
1188
|
+
let a;
|
|
1189
|
+
let b;
|
|
1190
|
+
let c;
|
|
1191
|
+
let d;
|
|
1192
|
+
let lis;
|
|
1193
|
+
|
|
1194
|
+
beforeEach(() => {
|
|
1195
|
+
element = $compile(
|
|
1196
|
+
"<ul>" + '<li ng-repeat="item in items">{{item}}</li>' + "</ul>",
|
|
1197
|
+
)(scope);
|
|
1198
|
+
a = 1;
|
|
1199
|
+
b = 2;
|
|
1200
|
+
c = 3;
|
|
1201
|
+
d = 4;
|
|
1202
|
+
|
|
1203
|
+
scope.items = [a, b, c];
|
|
1204
|
+
scope.$digest();
|
|
1205
|
+
lis = element.find("li");
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
it("should preserve the order of elements", () => {
|
|
1209
|
+
scope.items = [a, c, d];
|
|
1210
|
+
scope.$digest();
|
|
1211
|
+
const newElements = element.find("li");
|
|
1212
|
+
expect(newElements[0]).toEqual(lis[0]);
|
|
1213
|
+
expect(newElements[1]).toEqual(lis[2]);
|
|
1214
|
+
expect(newElements[2]).not.toEqual(lis[1]);
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
it("should throw error on adding existing duplicates and recover", () => {
|
|
1218
|
+
scope.items = [a, a, a];
|
|
1219
|
+
scope.$digest();
|
|
1220
|
+
expect(logs.shift().message).toMatch(/Duplicate key/);
|
|
1221
|
+
|
|
1222
|
+
// recover
|
|
1223
|
+
scope.items = [a];
|
|
1224
|
+
scope.$digest();
|
|
1225
|
+
let newElements = element.find("li");
|
|
1226
|
+
expect(newElements.length).toEqual(1);
|
|
1227
|
+
expect(newElements[0]).toEqual(lis[0]);
|
|
1228
|
+
|
|
1229
|
+
scope.items = [];
|
|
1230
|
+
scope.$digest();
|
|
1231
|
+
newElements = element.find("li");
|
|
1232
|
+
expect(newElements.length).toEqual(0);
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
it("should throw error on new duplicates and recover", () => {
|
|
1236
|
+
scope.items = [d, d, d];
|
|
1237
|
+
scope.$digest();
|
|
1238
|
+
expect(logs.shift().message).toMatch(/Duplicate key/);
|
|
1239
|
+
|
|
1240
|
+
// recover
|
|
1241
|
+
scope.items = [a];
|
|
1242
|
+
scope.$digest();
|
|
1243
|
+
let newElements = element.find("li");
|
|
1244
|
+
expect(newElements.length).toEqual(1);
|
|
1245
|
+
expect(newElements[0]).toEqual(lis[0]);
|
|
1246
|
+
|
|
1247
|
+
scope.items = [];
|
|
1248
|
+
scope.$digest();
|
|
1249
|
+
newElements = element.find("li");
|
|
1250
|
+
expect(newElements.length).toEqual(0);
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
it("should reverse items when the collection is reversed", () => {
|
|
1254
|
+
scope.items = [a, b, c];
|
|
1255
|
+
scope.$digest();
|
|
1256
|
+
lis = element.find("li");
|
|
1257
|
+
|
|
1258
|
+
scope.items = [c, b, a];
|
|
1259
|
+
scope.$digest();
|
|
1260
|
+
const newElements = element.find("li");
|
|
1261
|
+
expect(newElements.length).toEqual(3);
|
|
1262
|
+
expect(newElements[0]).toEqual(lis[2]);
|
|
1263
|
+
expect(newElements[1]).toEqual(lis[1]);
|
|
1264
|
+
expect(newElements[2]).toEqual(lis[0]);
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
it("should reuse elements even when model is composed of primitives", () => {
|
|
1268
|
+
// rebuilding repeater from scratch can be expensive, we should try to avoid it even for
|
|
1269
|
+
// model that is composed of primitives.
|
|
1270
|
+
|
|
1271
|
+
scope.items = ["hello", "cau", "ahoj"];
|
|
1272
|
+
scope.$digest();
|
|
1273
|
+
lis = element.find("li");
|
|
1274
|
+
lis[2].id = "yes";
|
|
1275
|
+
|
|
1276
|
+
scope.items = ["ahoj", "hello", "cau"];
|
|
1277
|
+
scope.$digest();
|
|
1278
|
+
const newLis = element.find("li");
|
|
1279
|
+
expect(newLis.length).toEqual(3);
|
|
1280
|
+
expect(newLis[0]).toEqual(lis[2]);
|
|
1281
|
+
expect(newLis[1]).toEqual(lis[0]);
|
|
1282
|
+
expect(newLis[2]).toEqual(lis[1]);
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
it("should be stable even if the collection is initially undefined", () => {
|
|
1286
|
+
scope.items = undefined;
|
|
1287
|
+
scope.$digest();
|
|
1288
|
+
|
|
1289
|
+
scope.items = [{ name: "A" }, { name: "B" }, { name: "C" }];
|
|
1290
|
+
scope.$digest();
|
|
1291
|
+
|
|
1292
|
+
lis = element.find("li");
|
|
1293
|
+
scope.items.shift();
|
|
1294
|
+
scope.$digest();
|
|
1295
|
+
|
|
1296
|
+
const newLis = element.find("li");
|
|
1297
|
+
expect(newLis.length).toBe(2);
|
|
1298
|
+
expect(newLis[0]).toBe(lis[1]);
|
|
1299
|
+
});
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
describe("compatibility", () => {
|
|
1303
|
+
it("should allow mixing ngRepeat and another element transclusion directive", () => {
|
|
1304
|
+
$compileProvider.directive(
|
|
1305
|
+
"elmTrans",
|
|
1306
|
+
valueFn({
|
|
1307
|
+
transclude: "element",
|
|
1308
|
+
controller($transclude, $scope, $element) {
|
|
1309
|
+
$transclude((transcludedNodes) => {
|
|
1310
|
+
$element.after("]]").after(transcludedNodes).after("[[");
|
|
1311
|
+
});
|
|
1312
|
+
},
|
|
1313
|
+
}),
|
|
1314
|
+
);
|
|
1315
|
+
|
|
1316
|
+
$compile = injector.get("$compile");
|
|
1317
|
+
|
|
1318
|
+
element = $compile(
|
|
1319
|
+
'<div><div ng-repeat="i in [1,2]" elm-trans>{{i}}</div></div>',
|
|
1320
|
+
)(scope);
|
|
1321
|
+
scope.$digest();
|
|
1322
|
+
expect(element.text()).toBe("[[1]][[2]]");
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
it("should allow mixing ngRepeat with ngInclude", (done) => {
|
|
1326
|
+
window.angular = new Angular();
|
|
1327
|
+
publishExternalAPI();
|
|
1328
|
+
|
|
1329
|
+
element = jqLite(
|
|
1330
|
+
'<div><div ng-repeat="i in [1,2]" ng-include="\'/test.html\'"></div></div>',
|
|
1331
|
+
);
|
|
1332
|
+
const injector = window.angular.bootstrap(element);
|
|
1333
|
+
scope = injector.get("$rootScope");
|
|
1334
|
+
$templateCache = injector.get("$templateCache");
|
|
1335
|
+
$templateCache.put("test.html", "hello");
|
|
1336
|
+
scope.$digest();
|
|
1337
|
+
setTimeout(() => {
|
|
1338
|
+
expect(element.text()).toBe("hellohello");
|
|
1339
|
+
done();
|
|
1340
|
+
}, 500);
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
it("should allow mixing ngRepeat with ngIf", () => {
|
|
1344
|
+
element = $compile(
|
|
1345
|
+
'<div><div ng-repeat="i in [1,2,3,4]" ng-if="i % 2 === 0">{{i}};</div></div>',
|
|
1346
|
+
)(scope);
|
|
1347
|
+
scope.$digest();
|
|
1348
|
+
expect(element.text()).toBe("2;4;");
|
|
1349
|
+
});
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
describe("ngRepeatStart", () => {
|
|
1353
|
+
it("should grow multi-node repeater", () => {
|
|
1354
|
+
scope.show = false;
|
|
1355
|
+
scope.books = [
|
|
1356
|
+
{ title: "T1", description: "D1" },
|
|
1357
|
+
{ title: "T2", description: "D2" },
|
|
1358
|
+
];
|
|
1359
|
+
element = $compile(
|
|
1360
|
+
"<div>" +
|
|
1361
|
+
'<dt ng-repeat-start="book in books">{{book.title}}:</dt>' +
|
|
1362
|
+
"<dd ng-repeat-end>{{book.description}};</dd>" +
|
|
1363
|
+
"</div>",
|
|
1364
|
+
)(scope);
|
|
1365
|
+
|
|
1366
|
+
scope.$digest();
|
|
1367
|
+
expect(element.text()).toEqual("T1:D1;T2:D2;");
|
|
1368
|
+
scope.books.push({ title: "T3", description: "D3" });
|
|
1369
|
+
scope.$digest();
|
|
1370
|
+
expect(element.text()).toEqual("T1:D1;T2:D2;T3:D3;");
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
it("should not clobber ng-if when updating collection", () => {
|
|
1374
|
+
scope.values = [1, 2, 3];
|
|
1375
|
+
scope.showMe = true;
|
|
1376
|
+
|
|
1377
|
+
element = $compile(
|
|
1378
|
+
"<div>" +
|
|
1379
|
+
'<div ng-repeat-start="val in values">val:{{val}};</div>' +
|
|
1380
|
+
'<div ng-if="showMe" ng-repeat-end>if:{{val}};</div>' +
|
|
1381
|
+
"</div>",
|
|
1382
|
+
)(scope);
|
|
1383
|
+
|
|
1384
|
+
scope.$digest();
|
|
1385
|
+
expect(element.find("div").length).toBe(6);
|
|
1386
|
+
|
|
1387
|
+
scope.values.shift();
|
|
1388
|
+
scope.values.push(4);
|
|
1389
|
+
|
|
1390
|
+
scope.$digest();
|
|
1391
|
+
expect(element.find("div").length).toBe(6);
|
|
1392
|
+
expect(element.text()).not.toContain("if:1;");
|
|
1393
|
+
});
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
describe("ngRepeat and transcludes", () => {
|
|
1397
|
+
it("should allow access to directive controller from children when used in a replace template", () => {
|
|
1398
|
+
let controller;
|
|
1399
|
+
$compileProvider
|
|
1400
|
+
.directive(
|
|
1401
|
+
"template",
|
|
1402
|
+
valueFn({
|
|
1403
|
+
template: '<div ng-repeat="l in [1]"><span test></span></div>',
|
|
1404
|
+
replace: true,
|
|
1405
|
+
controller() {
|
|
1406
|
+
this.flag = true;
|
|
1407
|
+
},
|
|
1408
|
+
}),
|
|
1409
|
+
)
|
|
1410
|
+
.directive(
|
|
1411
|
+
"test",
|
|
1412
|
+
valueFn({
|
|
1413
|
+
require: "^template",
|
|
1414
|
+
link(scope, el, attr, ctrl) {
|
|
1415
|
+
controller = ctrl;
|
|
1416
|
+
},
|
|
1417
|
+
}),
|
|
1418
|
+
);
|
|
1419
|
+
|
|
1420
|
+
injector.invoke(($compile, $rootScope) => {
|
|
1421
|
+
const element = $compile("<div><div template></div></div>")($rootScope);
|
|
1422
|
+
$rootScope.$apply();
|
|
1423
|
+
expect(controller.flag).toBe(true);
|
|
1424
|
+
dealoc(element);
|
|
1425
|
+
});
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
it("should use the correct transcluded scope", () => {
|
|
1429
|
+
$compileProvider.directive(
|
|
1430
|
+
"iso",
|
|
1431
|
+
valueFn({
|
|
1432
|
+
restrict: "E",
|
|
1433
|
+
transclude: true,
|
|
1434
|
+
template: '<div ng-repeat="a in [1]"><div ng-transclude></div></div>',
|
|
1435
|
+
scope: {},
|
|
1436
|
+
}),
|
|
1437
|
+
);
|
|
1438
|
+
injector.invoke(($compile, $rootScope) => {
|
|
1439
|
+
$rootScope.val = "transcluded content";
|
|
1440
|
+
const element = $compile('<iso><span ng-bind="val"></span></iso>')(
|
|
1441
|
+
$rootScope,
|
|
1442
|
+
);
|
|
1443
|
+
$rootScope.$digest();
|
|
1444
|
+
expect(element.text().trim()).toEqual("transcluded content");
|
|
1445
|
+
dealoc(element);
|
|
1446
|
+
});
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
it("should set the state before linking", () => {
|
|
1450
|
+
$compileProvider.directive(
|
|
1451
|
+
"assertA",
|
|
1452
|
+
valueFn((scope) => {
|
|
1453
|
+
// This linking function asserts that a is set.
|
|
1454
|
+
// If we only test this by asserting binding, it will work even if the value is set later.
|
|
1455
|
+
expect(scope.a).toBeDefined();
|
|
1456
|
+
}),
|
|
1457
|
+
);
|
|
1458
|
+
injector.invoke(($compile, $rootScope) => {
|
|
1459
|
+
const element = $compile(
|
|
1460
|
+
'<div><span ng-repeat="a in [1]"><span assert-a></span></span></div>',
|
|
1461
|
+
)($rootScope);
|
|
1462
|
+
$rootScope.$digest();
|
|
1463
|
+
dealoc(element);
|
|
1464
|
+
});
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
it("should work with svg elements when the svg container is transcluded", () => {
|
|
1468
|
+
$compileProvider.directive("svgContainer", () => ({
|
|
1469
|
+
template: "<svg ng-transclude></svg>",
|
|
1470
|
+
replace: true,
|
|
1471
|
+
transclude: true,
|
|
1472
|
+
}));
|
|
1473
|
+
injector.invoke(($compile, $rootScope) => {
|
|
1474
|
+
const element = $compile(
|
|
1475
|
+
'<svg-container><circle ng-repeat="r in rows"></circle></svg-container>',
|
|
1476
|
+
)($rootScope);
|
|
1477
|
+
$rootScope.rows = [1];
|
|
1478
|
+
$rootScope.$apply();
|
|
1479
|
+
|
|
1480
|
+
const circle = element.find("circle");
|
|
1481
|
+
expect(circle[0].toString()).toMatch(/SVG/);
|
|
1482
|
+
dealoc(element);
|
|
1483
|
+
});
|
|
1484
|
+
});
|
|
1485
|
+
});
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
// describe("ngRepeat animations", () => {
|
|
1489
|
+
// let body;
|
|
1490
|
+
// let element;
|
|
1491
|
+
// let $rootElement;
|
|
1492
|
+
|
|
1493
|
+
// function html(content) {
|
|
1494
|
+
// $rootElement.html(content);
|
|
1495
|
+
// element = $rootElement.children().eq(0);
|
|
1496
|
+
// return element;
|
|
1497
|
+
// }
|
|
1498
|
+
|
|
1499
|
+
// beforeEach(module("ngAnimate"));
|
|
1500
|
+
// beforeEach(module("ngAnimateMock"));
|
|
1501
|
+
|
|
1502
|
+
// beforeEach(
|
|
1503
|
+
// module(
|
|
1504
|
+
// () =>
|
|
1505
|
+
// // we need to run animation on attached elements;
|
|
1506
|
+
// function (_$rootElement_) {
|
|
1507
|
+
// $rootElement = _$rootElement_;
|
|
1508
|
+
// body = jqLite(window.document.body);
|
|
1509
|
+
// body.append($rootElement);
|
|
1510
|
+
// },
|
|
1511
|
+
// ),
|
|
1512
|
+
// );
|
|
1513
|
+
|
|
1514
|
+
// afterEach(() => {
|
|
1515
|
+
// body.empty();
|
|
1516
|
+
// });
|
|
1517
|
+
|
|
1518
|
+
// it("should fire off the enter animation", inject((
|
|
1519
|
+
// $compile,
|
|
1520
|
+
// scope,
|
|
1521
|
+
// $animate,
|
|
1522
|
+
// ) => {
|
|
1523
|
+
// let item;
|
|
1524
|
+
|
|
1525
|
+
// element = $compile(
|
|
1526
|
+
// html(
|
|
1527
|
+
// "<div><div " +
|
|
1528
|
+
// 'ng-repeat="item in items">' +
|
|
1529
|
+
// "{{ item }}" +
|
|
1530
|
+
// "</div></div>",
|
|
1531
|
+
// ),
|
|
1532
|
+
// )(scope);
|
|
1533
|
+
|
|
1534
|
+
// scope.$digest(); // re-enable the animations;
|
|
1535
|
+
|
|
1536
|
+
// scope.items = ["1", "2", "3"];
|
|
1537
|
+
// scope.$digest();
|
|
1538
|
+
|
|
1539
|
+
// item = $animate.queue.shift();
|
|
1540
|
+
// expect(item.event).toBe("enter");
|
|
1541
|
+
// expect(item.element.text()).toBe("1");
|
|
1542
|
+
|
|
1543
|
+
// item = $animate.queue.shift();
|
|
1544
|
+
// expect(item.event).toBe("enter");
|
|
1545
|
+
// expect(item.element.text()).toBe("2");
|
|
1546
|
+
|
|
1547
|
+
// item = $animate.queue.shift();
|
|
1548
|
+
// expect(item.event).toBe("enter");
|
|
1549
|
+
// expect(item.element.text()).toBe("3");
|
|
1550
|
+
// }));
|
|
1551
|
+
|
|
1552
|
+
// it("should fire off the leave animation", inject((
|
|
1553
|
+
// $compile,
|
|
1554
|
+
// scope,
|
|
1555
|
+
// $animate,
|
|
1556
|
+
// ) => {
|
|
1557
|
+
// let item;
|
|
1558
|
+
|
|
1559
|
+
// element = $compile(
|
|
1560
|
+
// html(
|
|
1561
|
+
// "<div><div " +
|
|
1562
|
+
// 'ng-repeat="item in items">' +
|
|
1563
|
+
// "{{ item }}" +
|
|
1564
|
+
// "</div></div>",
|
|
1565
|
+
// ),
|
|
1566
|
+
// )(scope);
|
|
1567
|
+
|
|
1568
|
+
// scope.items = ["1", "2", "3"];
|
|
1569
|
+
// scope.$digest();
|
|
1570
|
+
|
|
1571
|
+
// item = $animate.queue.shift();
|
|
1572
|
+
// expect(item.event).toBe("enter");
|
|
1573
|
+
// expect(item.element.text()).toBe("1");
|
|
1574
|
+
|
|
1575
|
+
// item = $animate.queue.shift();
|
|
1576
|
+
// expect(item.event).toBe("enter");
|
|
1577
|
+
// expect(item.element.text()).toBe("2");
|
|
1578
|
+
|
|
1579
|
+
// item = $animate.queue.shift();
|
|
1580
|
+
// expect(item.event).toBe("enter");
|
|
1581
|
+
// expect(item.element.text()).toBe("3");
|
|
1582
|
+
|
|
1583
|
+
// scope.items = ["1", "3"];
|
|
1584
|
+
// scope.$digest();
|
|
1585
|
+
|
|
1586
|
+
// item = $animate.queue.shift();
|
|
1587
|
+
// expect(item.event).toBe("leave");
|
|
1588
|
+
// expect(item.element.text()).toBe("2");
|
|
1589
|
+
// }));
|
|
1590
|
+
|
|
1591
|
+
// it("should not change the position of the block that is being animated away via a leave animation", inject((
|
|
1592
|
+
// $compile,
|
|
1593
|
+
// scope,
|
|
1594
|
+
// $animate,
|
|
1595
|
+
// $document,
|
|
1596
|
+
// $sniffer,
|
|
1597
|
+
// $timeout,
|
|
1598
|
+
// ) => {
|
|
1599
|
+
// if (!$sniffer.transitions) return;
|
|
1600
|
+
|
|
1601
|
+
// let item;
|
|
1602
|
+
// const ss = createMockStyleSheet($document);
|
|
1603
|
+
|
|
1604
|
+
// try {
|
|
1605
|
+
// $animate.enabled(true);
|
|
1606
|
+
|
|
1607
|
+
// ss.addRule(
|
|
1608
|
+
// ".animate-me div",
|
|
1609
|
+
// "-webkit-transition:1s linear all; transition:1s linear all;",
|
|
1610
|
+
// );
|
|
1611
|
+
|
|
1612
|
+
// element = $compile(
|
|
1613
|
+
// html(
|
|
1614
|
+
// '<div class="animate-me">' +
|
|
1615
|
+
// '<div ng-repeat="item in items">{{ item }}</div>' +
|
|
1616
|
+
// "</div>",
|
|
1617
|
+
// ),
|
|
1618
|
+
// )(scope);
|
|
1619
|
+
|
|
1620
|
+
// scope.items = ["1", "2", "3"];
|
|
1621
|
+
// scope.$digest();
|
|
1622
|
+
// expect(element.text()).toBe("123");
|
|
1623
|
+
|
|
1624
|
+
// scope.items = ["1", "3"];
|
|
1625
|
+
// scope.$digest();
|
|
1626
|
+
|
|
1627
|
+
// expect(element.text()).toBe("123"); // the original order should be preserved
|
|
1628
|
+
// $animate.flush();
|
|
1629
|
+
// $timeout.flush(1500); // 1s * 1.5 closing buffer
|
|
1630
|
+
// expect(element.text()).toBe("13");
|
|
1631
|
+
// } finally {
|
|
1632
|
+
// ss.destroy();
|
|
1633
|
+
// }
|
|
1634
|
+
// }));
|
|
1635
|
+
|
|
1636
|
+
// it("should fire off the move animation", () => {
|
|
1637
|
+
// let item;
|
|
1638
|
+
|
|
1639
|
+
// element = $compile(
|
|
1640
|
+
// html(
|
|
1641
|
+
// "<div>" +
|
|
1642
|
+
// '<div ng-repeat="item in items">' +
|
|
1643
|
+
// "{{ item }}" +
|
|
1644
|
+
// "</div>" +
|
|
1645
|
+
// "</div>",
|
|
1646
|
+
// ),
|
|
1647
|
+
// )(scope);
|
|
1648
|
+
|
|
1649
|
+
// scope.items = ["1", "2", "3"];
|
|
1650
|
+
// scope.$digest();
|
|
1651
|
+
|
|
1652
|
+
// item = $animate.queue.shift();
|
|
1653
|
+
// expect(item.event).toBe("enter");
|
|
1654
|
+
// expect(item.element.text()).toBe("1");
|
|
1655
|
+
|
|
1656
|
+
// item = $animate.queue.shift();
|
|
1657
|
+
// expect(item.event).toBe("enter");
|
|
1658
|
+
// expect(item.element.text()).toBe("2");
|
|
1659
|
+
|
|
1660
|
+
// item = $animate.queue.shift();
|
|
1661
|
+
// expect(item.event).toBe("enter");
|
|
1662
|
+
// expect(item.element.text()).toBe("3");
|
|
1663
|
+
|
|
1664
|
+
// scope.items = ["2", "3", "1"];
|
|
1665
|
+
// scope.$digest();
|
|
1666
|
+
|
|
1667
|
+
// item = $animate.queue.shift();
|
|
1668
|
+
// expect(item.event).toBe("move");
|
|
1669
|
+
// expect(item.element.text()).toBe("2");
|
|
1670
|
+
|
|
1671
|
+
// item = $animate.queue.shift();
|
|
1672
|
+
// expect(item.event).toBe("move");
|
|
1673
|
+
// expect(item.element.text()).toBe("3");
|
|
1674
|
+
// });
|
|
1675
|
+
// });
|