Commit 79e9904058f514a0a3956caeab3705e35d78ae34
1 parent
ee6a9002
Exists in
master
and in
35 other branches
versão inicial com testes
Showing
7 changed files
with
316 additions
and
96 deletions
Show diff stats
karma.conf.js
1 | +/* global process, */ | ||
1 | 'use strict'; | 2 | 'use strict'; |
2 | 3 | ||
3 | var path = require('path'); | 4 | var path = require('path'); |
@@ -7,104 +8,160 @@ var _ = require('lodash'); | @@ -7,104 +8,160 @@ var _ = require('lodash'); | ||
7 | var wiredep = require('wiredep'); | 8 | var wiredep = require('wiredep'); |
8 | 9 | ||
9 | var pathSrcHtml = [ | 10 | var pathSrcHtml = [ |
10 | - path.join(conf.paths.src, '/**/*.html') | 11 | + path.join(conf.paths.src, '/**/*.html') |
11 | ]; | 12 | ]; |
12 | 13 | ||
13 | function listFiles() { | 14 | function listFiles() { |
14 | - var wiredepOptions = _.extend({}, conf.wiredep, { | ||
15 | - dependencies: true, | ||
16 | - devDependencies: true | ||
17 | - }); | ||
18 | - | ||
19 | - var patterns = wiredep(wiredepOptions).js | ||
20 | - .concat([ | ||
21 | - path.join(conf.paths.src, '/app/**/*.module.js'), | ||
22 | - path.join(conf.paths.src, '/app/**/*.js'), | ||
23 | - path.join(conf.paths.src, '/**/*.spec.js'), | ||
24 | - path.join(conf.paths.src, '/**/*.mock.js'), | ||
25 | - ]) | ||
26 | - .concat(pathSrcHtml); | ||
27 | - | ||
28 | - var files = patterns.map(function(pattern) { | ||
29 | - return { | ||
30 | - pattern: pattern | ||
31 | - }; | ||
32 | - }); | ||
33 | - files.push({ | ||
34 | - pattern: path.join(conf.paths.src, '/assets/**/*'), | ||
35 | - included: false, | ||
36 | - served: true, | ||
37 | - watched: false | ||
38 | - }); | ||
39 | - return files; | 15 | + var wiredepOptions = _.extend({}, conf.wiredep, { |
16 | + dependencies: true, | ||
17 | + devDependencies: true | ||
18 | + }); | ||
19 | + | ||
20 | + var patterns = wiredep(wiredepOptions).js | ||
21 | + .concat([ | ||
22 | + path.join(conf.paths.src, 'noosfero.js') | ||
23 | + ,path.join(conf.paths.src, 'noosfero-testing.js'), | ||
24 | + // path.join(conf.paths.src, '/app/**/*.module.js'), | ||
25 | + // path.join(conf.paths.src, '/app/**/*.js'), | ||
26 | + // path.join(conf.paths.src, '/**/*.spec.js'), | ||
27 | + // path.join(conf.paths.src, '/**/*.mock.js') | ||
28 | + | ||
29 | + ]) | ||
30 | + .concat(pathSrcHtml); | ||
31 | + | ||
32 | + var files = patterns.map(function (pattern) { | ||
33 | + return { | ||
34 | + pattern: pattern | ||
35 | + }; | ||
36 | + }); | ||
37 | + files.push({ | ||
38 | + pattern: path.join(conf.paths.src, '/assets/**/*'), | ||
39 | + included: false, | ||
40 | + served: true, | ||
41 | + watched: false | ||
42 | + }); | ||
43 | + files.push({ | ||
44 | + pattern: path.join(conf.paths.src, '/*.map'), | ||
45 | + included: false, | ||
46 | + served: true | ||
47 | + }); | ||
48 | + return files; | ||
40 | } | 49 | } |
41 | 50 | ||
42 | -module.exports = function(config) { | ||
43 | - | ||
44 | - var configuration = { | ||
45 | - files: listFiles(), | ||
46 | - | ||
47 | - singleRun: true, | ||
48 | - | ||
49 | - autoWatch: false, | ||
50 | - | ||
51 | - ngHtml2JsPreprocessor: { | ||
52 | - stripPrefix: conf.paths.src + '/', | ||
53 | - moduleName: 'angular' | ||
54 | - }, | ||
55 | - | ||
56 | - logLevel: 'WARN', | ||
57 | - | ||
58 | - frameworks: ['jasmine', 'angular-filesort'], | ||
59 | - | ||
60 | - angularFilesort: { | ||
61 | - whitelist: [path.join(conf.paths.src, '/**/!(*.html|*.spec|*.mock).js')] | ||
62 | - }, | ||
63 | - | ||
64 | - browsers : ['PhantomJS'], | ||
65 | - | ||
66 | - plugins : [ | ||
67 | - 'karma-phantomjs-launcher', | ||
68 | - 'karma-angular-filesort', | ||
69 | - 'karma-coverage', | ||
70 | - 'karma-jasmine', | ||
71 | - 'karma-ng-html2js-preprocessor' | ||
72 | - ], | ||
73 | - | ||
74 | - coverageReporter: { | ||
75 | - type : 'html', | ||
76 | - dir : 'coverage/' | ||
77 | - }, | 51 | +var webpackConfig = require("./webpack.config.js"); |
52 | + | ||
53 | +module.exports = function (config) { | ||
54 | + | ||
55 | + var configuration = { | ||
56 | + files: listFiles(), | ||
57 | + | ||
58 | + singleRun: false, | ||
59 | + | ||
60 | + autoWatch: true, | ||
61 | + colors: true, | ||
62 | + | ||
63 | + logLevel: config.LOG_INFO, | ||
64 | + | ||
65 | + ngHtml2JsPreprocessor: { | ||
66 | + stripPrefix: conf.paths.src + '/', | ||
67 | + moduleName: 'angular' | ||
68 | + }, | ||
69 | + | ||
70 | + | ||
71 | + frameworks: ['jasmine'],//, 'angular-filesort'], | ||
72 | + | ||
73 | + angularFilesort: { | ||
74 | + whitelist: [path.join(conf.paths.src, '/**/!(*.html|*.spec|*.mock).js')] | ||
75 | + }, | ||
76 | + | ||
77 | + browsers: ['Chrome'], | ||
78 | + | ||
79 | + webpack: _.merge({}, webpackConfig, { | ||
80 | + externals: { | ||
81 | + encapsulatedWindow: 'Object.create(window)' | ||
82 | + | ||
83 | + }, | ||
84 | + resolve: { | ||
85 | + alias : { | ||
86 | + angular: "angular/angular.js" | ||
87 | + } | ||
88 | + }, | ||
89 | + module: { | ||
90 | + loaders: [ | ||
91 | + { | ||
92 | + test: /angular\.js$/, | ||
93 | + loaders:[ | ||
94 | + 'imports?window=encapsulatedWindow', | ||
95 | + 'exports?window.angular' | ||
96 | + ], | ||
97 | + include: [new RegExp(__dirname + '/bower_components/angular/')] | ||
98 | + } | ||
99 | + ], | ||
100 | + postLoaders: [ | ||
101 | + { | ||
102 | + test: /src\/noosfero.js/, | ||
103 | + exclude: [ | ||
104 | + /node_modules\//, | ||
105 | + /bower_components\//, | ||
106 | + /src\/noosfero-testing.js/ | ||
107 | + ], | ||
108 | + loader: 'istanbul-instrumenter' | ||
109 | + } | ||
110 | + | ||
111 | + | ||
112 | + ] | ||
113 | + } | ||
114 | + }), | ||
115 | + webpackServer: { | ||
116 | + quite: true | ||
117 | + }, | ||
118 | + plugins: [ | ||
119 | + 'karma-chrome-launcher', | ||
120 | + 'karma-phantomjs-launcher', | ||
121 | + 'karma-angular-filesort', | ||
122 | + 'karma-webpack', | ||
123 | + 'karma-coverage', | ||
124 | + 'karma-jasmine', | ||
125 | + 'karma-ng-html2js-preprocessor' | ||
126 | + ], | ||
127 | + | ||
128 | + coverageReporter: { | ||
129 | + type: 'html', | ||
130 | + dir: 'coverage/' | ||
131 | + }, | ||
132 | + | ||
133 | + reporters: ['dots', "coverage"], | ||
134 | + | ||
135 | + proxies: { | ||
136 | + '/assets/': path.join('/base/', conf.paths.src, '/assets/') | ||
137 | + } | ||
138 | + }; | ||
78 | 139 | ||
79 | - reporters: ['progress'], | 140 | + // This is the default preprocessors configuration for a usage with Karma cli |
141 | + // The coverage preprocessor is added in gulp/unit-test.js only for single tests | ||
142 | + // It was not possible to do it there because karma doesn't let us now if we are | ||
143 | + // running a single test or not | ||
144 | + configuration.preprocessors = { | ||
145 | + 'src/**/*.[sS]pec.ts': ['webpack'] | ||
146 | + }; | ||
80 | 147 | ||
81 | - proxies: { | ||
82 | - '/assets/': path.join('/base/', conf.paths.src, '/assets/') | 148 | + pathSrcHtml.forEach(function (path) { |
149 | + configuration.preprocessors[path] = ['ng-html2js']; | ||
150 | + }); | ||
151 | + | ||
152 | + // This block is needed to execute Chrome on Travis | ||
153 | + // If you ever plan to use Chrome and Travis, you can keep it | ||
154 | + // If not, you can safely remove it | ||
155 | + // https://github.com/karma-runner/karma/issues/1144#issuecomment-53633076 | ||
156 | + if (configuration.browsers[0] === 'Chrome' && process.env.TRAVIS) { | ||
157 | + configuration.customLaunchers = { | ||
158 | + 'chrome-travis-ci': { | ||
159 | + base: 'Chrome', | ||
160 | + flags: ['--no-sandbox'] | ||
161 | + } | ||
162 | + }; | ||
163 | + configuration.browsers = ['chrome-travis-ci']; | ||
83 | } | 164 | } |
84 | - }; | ||
85 | - | ||
86 | - // This is the default preprocessors configuration for a usage with Karma cli | ||
87 | - // The coverage preprocessor is added in gulp/unit-test.js only for single tests | ||
88 | - // It was not possible to do it there because karma doesn't let us now if we are | ||
89 | - // running a single test or not | ||
90 | - configuration.preprocessors = {}; | ||
91 | - pathSrcHtml.forEach(function(path) { | ||
92 | - configuration.preprocessors[path] = ['ng-html2js']; | ||
93 | - }); | ||
94 | - | ||
95 | - // This block is needed to execute Chrome on Travis | ||
96 | - // If you ever plan to use Chrome and Travis, you can keep it | ||
97 | - // If not, you can safely remove it | ||
98 | - // https://github.com/karma-runner/karma/issues/1144#issuecomment-53633076 | ||
99 | - if(configuration.browsers[0] === 'Chrome' && process.env.TRAVIS) { | ||
100 | - configuration.customLaunchers = { | ||
101 | - 'chrome-travis-ci': { | ||
102 | - base: 'Chrome', | ||
103 | - flags: ['--no-sandbox'] | ||
104 | - } | ||
105 | - }; | ||
106 | - configuration.browsers = ['chrome-travis-ci']; | ||
107 | - } | ||
108 | 165 | ||
109 | - config.set(configuration); | 166 | + config.set(configuration); |
110 | }; | 167 | }; |
package.json
1 | { | 1 | { |
2 | - "name": "angular", | 2 | + "name": "angular-theme", |
3 | "version": "0.0.0", | 3 | "version": "0.0.0", |
4 | - "dependencies": {}, | 4 | + "dependencies": { |
5 | + "angular": "^1.5.0", | ||
6 | + "angular-mock": "^1.0.0", | ||
7 | + "moment": "^2.11.2" | ||
8 | + }, | ||
5 | "scripts": { | 9 | "scripts": { |
6 | "test": "gulp test", | 10 | "test": "gulp test", |
7 | "postinstall": "npm install -g bower && bower install", | 11 | "postinstall": "npm install -g bower && bower install", |
@@ -43,10 +47,12 @@ | @@ -43,10 +47,12 @@ | ||
43 | "http-proxy-middleware": "~0.9.0", | 47 | "http-proxy-middleware": "~0.9.0", |
44 | "karma": "~0.13.10", | 48 | "karma": "~0.13.10", |
45 | "karma-angular-filesort": "~1.0.0", | 49 | "karma-angular-filesort": "~1.0.0", |
46 | - "karma-coverage": "~0.5.2", | 50 | + "karma-chrome-launcher": "^0.2.2", |
51 | + "karma-coverage": "^0.5.3", | ||
47 | "karma-jasmine": "~0.3.6", | 52 | "karma-jasmine": "~0.3.6", |
48 | "karma-ng-html2js-preprocessor": "~0.2.0", | 53 | "karma-ng-html2js-preprocessor": "~0.2.0", |
49 | "karma-phantomjs-launcher": "~0.2.1", | 54 | "karma-phantomjs-launcher": "~0.2.1", |
55 | + "karma-webpack": "^1.7.0", | ||
50 | "lodash": "~3.10.1", | 56 | "lodash": "~3.10.1", |
51 | "main-bower-files": "~2.9.0", | 57 | "main-bower-files": "~2.9.0", |
52 | "ng-forward": "0.0.1-alpha.12", | 58 | "ng-forward": "0.0.1-alpha.12", |
src/app/components/noosfero-articles/article/article.directive.spec.ts
0 → 100644
@@ -0,0 +1,91 @@ | @@ -0,0 +1,91 @@ | ||
1 | +let oldDefine = Object.defineProperties; | ||
2 | + | ||
3 | +Object.defineProperties = function(object, properties){ | ||
4 | + let filteredProps = {}; | ||
5 | + let currentProperties = Object.getOwnPropertyNames(object); | ||
6 | + for (let i = 0; i < currentProperties.length; i++) { | ||
7 | + let prop = currentProperties[i]; | ||
8 | + if(currentProperties.indexOf(prop) < 0){ | ||
9 | + filteredProps[prop] = properties[prop]; | ||
10 | + } | ||
11 | + } | ||
12 | + oldDefine(object, <any>filteredProps); | ||
13 | +}; | ||
14 | + | ||
15 | +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder'; | ||
16 | +import {Input, provide, Component} from 'ng-forward'; | ||
17 | + | ||
18 | +import {ArticleDirective} from './article.directive'; | ||
19 | + | ||
20 | + | ||
21 | + | ||
22 | + | ||
23 | + | ||
24 | + | ||
25 | +// Instantiate the Builder, this part is different than ng2. | ||
26 | +// In ng2 you inject tcb. | ||
27 | +const tcb = new TestComponentBuilder(); | ||
28 | + | ||
29 | + | ||
30 | +// Create your test bed | ||
31 | +@Component({ selector: 'my-test' , template: '<hr />'}) | ||
32 | +class TestArticleDirective { | ||
33 | + article = { type: 'TinyMceArticle' }; | ||
34 | + profile = { name: 'profile-name' }; | ||
35 | + constructor(){ | ||
36 | + | ||
37 | + } | ||
38 | +} | ||
39 | + | ||
40 | + | ||
41 | + | ||
42 | +describe("Article Directive", () => { | ||
43 | + it("does something", done => { | ||
44 | + let html = '<noosfero-article [article]="ctr.article" [profile]="ctrl.profile"></noosfero-article>'; | ||
45 | + tcb | ||
46 | + .overrideTemplate(TestArticleDirective, html) | ||
47 | + .createAsync(TestArticleDirective).then(fixture => { | ||
48 | + let myComponent: ArticleDirective = fixture.componentInstance; | ||
49 | + expect(myComponent.article.type).toEqual("TinyMceArticle"); | ||
50 | + expect(myComponent.profile.name).toEqual("profile-name"); | ||
51 | + console.log(fixture.debugElement); | ||
52 | + done(); | ||
53 | + }); | ||
54 | + }); | ||
55 | + | ||
56 | + // let html = '<noosfero-article [profile]=profile [artcile]=article></noosfero-article>'; | ||
57 | + | ||
58 | + // let noosferoApp: ng.IModule; | ||
59 | + // let articleDirective: ArticleDirective; | ||
60 | + // let $scope: ng.IScope; | ||
61 | + // let element: ng.IAugmentedJQuery; | ||
62 | + // beforeEach(angular.mock.module("noosferoApp")); | ||
63 | + | ||
64 | + // beforeEach(inject(function($controller: ng.IControllerService, $injector: ng.auto.IInjectorService, $rootScope, $q, $compile, $location, toastr) { | ||
65 | + // let routeParams = {}; | ||
66 | + // let $scope: ng.IScope = $rootScope.$new(); | ||
67 | + // (<any>$scope).ctrl = { | ||
68 | + // article: { | ||
69 | + // type: 'article' | ||
70 | + // }, | ||
71 | + // profile: { } | ||
72 | + // }; | ||
73 | + // element = getCompiledElement($scope, $compile); | ||
74 | + // //let element = angular.element('<noosfero-article article="ctrl.article" [profile]="ctrl.profile"></noosfero-article>'); | ||
75 | + // //articleDirective = new ArticleDirective(element, $scope, $injector, $compile); | ||
76 | + // console.log(articleDirective); | ||
77 | + // })); | ||
78 | + | ||
79 | + // it("renders accordly the article type", (done) => { | ||
80 | + // expect(1 + 1).toEqual(2); | ||
81 | + // console.log(noosferoApp); | ||
82 | + // done(); | ||
83 | + // }); | ||
84 | + | ||
85 | + // function getCompiledElement($scope, $compile) { | ||
86 | + // let element = angular.element('<noosfero-article [article]="ctrl.article" [profile]="ctrl.profile"></noosfero-article>'); | ||
87 | + // let compiledElement = $compile(element)($scope); | ||
88 | + // $scope.$digest(); | ||
89 | + // return compiledElement; | ||
90 | + // } | ||
91 | +}); | ||
0 | \ No newline at end of file | 92 | \ No newline at end of file |
src/app/index.ts
1 | + | ||
1 | import "reflect-metadata"; | 2 | import "reflect-metadata"; |
2 | import {NoosferoApp} from "./index.module"; | 3 | import {NoosferoApp} from "./index.module"; |
3 | import {noosferoModuleConfig} from "./index.config"; | 4 | import {noosferoModuleConfig} from "./index.config"; |
@@ -11,7 +12,9 @@ import {ProfileInfo as noosferoProfileInfo} from "./profile-info/profile-info.co | @@ -11,7 +12,9 @@ import {ProfileInfo as noosferoProfileInfo} from "./profile-info/profile-info.co | ||
11 | import {Main} from "./main/main.component"; | 12 | import {Main} from "./main/main.component"; |
12 | import {bootstrap, bundle} from "ng-forward"; | 13 | import {bootstrap, bundle} from "ng-forward"; |
13 | 14 | ||
14 | -declare var moment: any; | 15 | + |
16 | + | ||
17 | +declare var moment: any; | ||
15 | 18 | ||
16 | let noosferoApp: any = bundle("noosferoApp", Main, ["ngAnimate", "ngCookies", "ngStorage", "ngTouch", | 19 | let noosferoApp: any = bundle("noosferoApp", Main, ["ngAnimate", "ngCookies", "ngStorage", "ngTouch", |
17 | "ngSanitize", "ngMessages", "ngAria", "restangular", | 20 | "ngSanitize", "ngMessages", "ngAria", "restangular", |
@@ -0,0 +1,54 @@ | @@ -0,0 +1,54 @@ | ||
1 | +{ | ||
2 | + "rules": { | ||
3 | + "class-name": true, | ||
4 | + "comment-format": [ | ||
5 | + true, | ||
6 | + "check-space" | ||
7 | + ], | ||
8 | + "indent": [ | ||
9 | + true, | ||
10 | + "spaces" | ||
11 | + ], | ||
12 | + "no-duplicate-variable": true, | ||
13 | + "no-eval": true, | ||
14 | + "no-internal-module": true, | ||
15 | + "no-trailing-whitespace": true, | ||
16 | + "no-var-keyword": true, | ||
17 | + "one-line": [ | ||
18 | + true, | ||
19 | + "check-open-brace", | ||
20 | + "check-whitespace" | ||
21 | + ], | ||
22 | + "quotemark": [ | ||
23 | + false, | ||
24 | + "double" | ||
25 | + ], | ||
26 | + "semicolon": true, | ||
27 | + "triple-equals": [ | ||
28 | + true, | ||
29 | + "allow-null-check" | ||
30 | + ], | ||
31 | + "typedef-whitespace": [ | ||
32 | + true, | ||
33 | + { | ||
34 | + "call-signature": "nospace", | ||
35 | + "index-signature": "nospace", | ||
36 | + "parameter": "nospace", | ||
37 | + "property-declaration": "nospace", | ||
38 | + "variable-declaration": "nospace" | ||
39 | + } | ||
40 | + ], | ||
41 | + "variable-name": [ | ||
42 | + true, | ||
43 | + "ban-keywords" | ||
44 | + ], | ||
45 | + "whitespace": [ | ||
46 | + true, | ||
47 | + "check-branch", | ||
48 | + "check-decl", | ||
49 | + "check-operator", | ||
50 | + "check-separator", | ||
51 | + "check-type" | ||
52 | + ] | ||
53 | + } | ||
54 | +} | ||
0 | \ No newline at end of file | 55 | \ No newline at end of file |
typings.json
@@ -4,8 +4,10 @@ | @@ -4,8 +4,10 @@ | ||
4 | "devDependencies": {}, | 4 | "devDependencies": {}, |
5 | "ambientDependencies": { | 5 | "ambientDependencies": { |
6 | "angular": "github:DefinitelyTyped/DefinitelyTyped/angularjs/angular.d.ts#1c4a34873c9e70cce86edd0e61c559e43dfa5f75", | 6 | "angular": "github:DefinitelyTyped/DefinitelyTyped/angularjs/angular.d.ts#1c4a34873c9e70cce86edd0e61c559e43dfa5f75", |
7 | + "angular-mocks": "github:DefinitelyTyped/DefinitelyTyped/angularjs/angular-mocks.d.ts", | ||
7 | "angular-ui-router": "github:DefinitelyTyped/DefinitelyTyped/angular-ui-router/angular-ui-router.d.ts#655f8c1bf3c71b0e1ba415b36309604f79326ac8", | 8 | "angular-ui-router": "github:DefinitelyTyped/DefinitelyTyped/angular-ui-router/angular-ui-router.d.ts#655f8c1bf3c71b0e1ba415b36309604f79326ac8", |
8 | "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c", | 9 | "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c", |
10 | + "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#dd638012d63e069f2c99d06ef4dcc9616a943ee4", | ||
9 | "jquery": "github:DefinitelyTyped/DefinitelyTyped/jquery/jquery.d.ts#470954c4f427e0805a2d633636a7c6aa7170def8", | 11 | "jquery": "github:DefinitelyTyped/DefinitelyTyped/jquery/jquery.d.ts#470954c4f427e0805a2d633636a7c6aa7170def8", |
10 | "restangular": "github:DefinitelyTyped/DefinitelyTyped/restangular/restangular.d.ts#f2ae460e1751bb6668d291d4eb9255f047dd0ac5" | 12 | "restangular": "github:DefinitelyTyped/DefinitelyTyped/restangular/restangular.d.ts#f2ae460e1751bb6668d291d4eb9255f047dd0ac5" |
11 | } | 13 | } |
webpack.config.js
1 | +/* global __dirname */ | ||
2 | + | ||
1 | var argv = require("yargs").argv; | 3 | var argv = require("yargs").argv; |
2 | var path = require("path"); | 4 | var path = require("path"); |
5 | +var glob = require("glob"); | ||
6 | + | ||
3 | 7 | ||
4 | var extension = ".js"; | 8 | var extension = ".js"; |
5 | if (argv.production) { | 9 | if (argv.production) { |
@@ -13,9 +17,12 @@ var uglifyLoaderConfig = { | @@ -13,9 +17,12 @@ var uglifyLoaderConfig = { | ||
13 | loader: "uglify" | 17 | loader: "uglify" |
14 | }; | 18 | }; |
15 | 19 | ||
20 | +var testingFiles = glob.sync("./src/app/**/*.[sS]pec.ts"); | ||
21 | + | ||
16 | var webpackConfig = { | 22 | var webpackConfig = { |
17 | entry: { | 23 | entry: { |
18 | - noosfero: './src/app/index.ts' | 24 | + noosfero: './src/app/index.ts', |
25 | + 'noosfero-testing': testingFiles | ||
19 | }, | 26 | }, |
20 | 27 | ||
21 | 28 |