Merge branch 'master' into releases

This commit is contained in:
Roman Hotsiy 2016-02-03 17:05:18 +02:00
commit 132974acc0
10 changed files with 204 additions and 63 deletions

View File

@ -12,7 +12,7 @@ gulp.task('test-server', function (done) {
baseDir: './tests/e2e', baseDir: './tests/e2e',
routes: { routes: {
'/dist': './dist', '/dist': './dist',
'/swagger.json': './demo/swagger.json' '/swagger.yml': './demo/swagger.yml'
}, },
} }
}, done); }, done);

View File

@ -169,6 +169,21 @@
description: "Pet not found" description: "Pet not found"
405: 405:
description: "Validation exception" description: "Validation exception"
x-code-samples:
-
lang: PHP
source: |-
$form = new \PetStore\Entities\Pet();
$form->setPetId(1);
$form->setPetType("Dog");
$form->setName("Rex");
// set other fields
try {
$pet = $client->pets()->update($form);
} catch (UnprocessableEntityException $e) {
var_dump($e->getErrors());
}
security: security:
- -
petstore_auth: petstore_auth:

View File

@ -1,27 +1,29 @@
# ReDoc vendor extensions # ReDoc vendor extensions
ReDoc makes use of the following [vendor extensions](http://swagger.io/specification/#vendorExtensions) ReDoc makes use of the following [vendor extensions](http://swagger.io/specification/#vendorExtensions)
### <a name="infoObject"></a> [Info Object](http://swagger.io/specification/#infoObject) vendor extensions ### <a name="infoObject"></a>[Info Object](http://swagger.io/specification/#infoObject) vendor extensions
#### <a name="x-logo"></a> x-logo #### <a name="x-logo"></a> x-logo
| Field Name | Type | Description
| :------------- | :------: |
| x-logo | [Logo Object](#logoObject) | The information about API logo
##### Usage in Redoc | Field Name | Type | Description |
| :------------- | :-----------: | :---------- |
| x-logo | [Logo Object](#logoObject) | The information about API logo |
###### Usage in Redoc
`x-logo` is used to specify API logo. The corresponding image are displayed just above side-menu. `x-logo` is used to specify API logo. The corresponding image are displayed just above side-menu.
#### <a name="logoObject"></a> Logo Object #### <a name="logoObject"></a>Logo Object
The information about API logo The information about API logo
##### Fixed fields ###### Fixed fields
| Field Name | Type | Description | Field Name | Type | Description |
| :---------- | :------: | | :-------------- | :------: | :---------- |
| url | string | The URL pointing to the spec logo. MUST be in the format of a URL | url | string | The URL pointing to the spec logo. MUST be in the format of a URL
| backgroundColor | string | background color to be used. MUST be in [CSS color syntax](https://developer.mozilla.org/en/docs/Web/CSS/color) | backgroundColor | string | background color to be used. MUST be in [CSS color syntax](https://developer.mozilla.org/en/docs/Web/CSS/color) |
##### x-logo example ###### x-logo example
```yaml json
```json
{ {
"info": { "info": {
"version": "1.0.0", "version": "1.0.0",
@ -33,15 +35,14 @@ The information about API logo
} }
} }
``` ```
yaml
```yaml ```yaml
{ info:
info:
version: "1.0.0" version: "1.0.0"
title: "Swagger Petstore" title: "Swagger Petstore"
x-logo: x-logo:
url: "https://rebilly.github.io/ReDoc/petstore-logo.png" url: "https://rebilly.github.io/ReDoc/petstore-logo.png"
backgroundColor: "white" backgroundColor: "white"
}
``` ```
@ -49,15 +50,16 @@ The information about API logo
### [Tag object](http://swagger.io/specification/#tagObject) vendor extensions ### [Tag object](http://swagger.io/specification/#tagObject) vendor extensions
#### <a name="x-traitTag"></a> x-traitTag #### <a name="x-traitTag"></a> x-traitTag
| Field Name | Type | Description | Field Name | Type | Description |
| :------------- | :------: | | :------------- | :------: | :---------- |
| x-traitTag | boolean | In Swagger two operations can have multiply tags. This property distinguish between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) | x-traitTag | boolean | In Swagger two operations can have multiply tags. This property distinguish between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) |
##### Usage in Redoc ###### Usage in Redoc
Tags that have `x-traitTag` set to `true` are listed in side-menu but don't have any subitems (operations). Tag `description` is rendered as well. Tags that have `x-traitTag` set to `true` are listed in side-menu but don't have any subitems (operations). Tag `description` is rendered as well.
This is useful for handling out common things like Pagination, Rate-Limits, etc. This is useful for handling out common things like Pagination, Rate-Limits, etc.
##### x-traitTag example ###### x-traitTag example
json
```json ```json
{ {
"name": "Pagination", "name": "Pagination",
@ -65,6 +67,7 @@ This is useful for handling out common things like Pagination, Rate-Limits, etc.
"x-traitTag": true "x-traitTag": true
} }
``` ```
yaml
```yaml ```yaml
name: Pagination name: Pagination
description: Pagination description (can use markdown syntax) description: Pagination description (can use markdown syntax)
@ -74,32 +77,32 @@ x-traitTag: true
### [Operation Object](http://swagger.io/specification/#operationObject) vendor extensions ### [Operation Object](http://swagger.io/specification/#operationObject) vendor extensions
#### <a name="x-code-samples"></a> x-code-samples #### <a name="x-code-samples"></a> x-code-samples
| Field Name | Type | Description | Field Name | Type | Description |
| :------------- | :------: | | :------------- | :------: | :---------- |
| x-code-samples | [[Code Sample Object](#codeSampleObject)] | A list of code samples associated with operation | x-code-samples | [ [Code Sample Object](#codeSampleObject) ] | A list of code samples associated with operation |
##### Usage in ReDoc ###### Usage in ReDoc
x-code-samples are rendered on the right panel of ReDoc `x-code-samples` are rendered on the right panel of ReDoc
#### <a name="codeSampleObject"></a> Code Sample Object #### <a name="codeSampleObject"></a>Code Sample Object
Operation code sample Operation code sample
##### Fixed fields ###### Fixed fields
| Field Name | Type | Description | Field Name | Type | Description |
| :---------- | :------: | :----------- | :---------- | :------: | :----------- |
| lang | string | Code sample language. Value should be one of the following [list](https://github.com/github/linguist/blob/master/lib/linguist/popular.yml) | lang | string | Code sample language. Value should be one of the following [list](https://github.com/github/linguist/blob/master/lib/linguist/popular.yml) |
| source | string | Code sample source code | source | string | Code sample source code |
##### Code Sample Object example ###### Code Sample Object example
```yaml json
```json
{ {
"lang": "JavaScript", "lang": "JavaScript",
"source": "console.log('Hello World');" "source": "console.log('Hello World');"
} }
``` ```
yaml
```yaml ```yaml
{ lang: JavaScript
lang: JavaScript source: console.log('Hello World');
source: console.log('Hello World');
}
``` ```

View File

@ -1,10 +1,11 @@
'use strict'; 'use strict';
import {Component, View} from 'angular2/core'; import {Component, View, EventEmitter} from 'angular2/core';
import {CORE_DIRECTIVES} from 'angular2/common'; import {CORE_DIRECTIVES} from 'angular2/common';
@Component({ @Component({
selector: 'tabs' selector: 'tabs',
events: ['change']
}) })
@View({ @View({
template: ` template: `
@ -20,13 +21,34 @@ import {CORE_DIRECTIVES} from 'angular2/common';
export class Tabs { export class Tabs {
constructor() { constructor() {
this.tabs = []; this.tabs = [];
this.change = new EventEmitter();
} }
selectTab(tab) { selectTab(tab, notify = true) {
if (tab.active) return;
this.tabs.forEach((tab) => { this.tabs.forEach((tab) => {
tab.active = false; tab.active = false;
}); });
tab.active = true; tab.active = true;
notify && this.change.next(tab.tabTitle);
}
selectyByTitle(tabTitle, notify = false) {
let prevActive;
let newActive;
this.tabs.forEach((tab) => {
if (tab.active) prevActive = tab;
tab.active = false;
if (tab.tabTitle === tabTitle) {
newActive = tab;
}
});
if (newActive) {
newActive.active = true;
} else {
prevActive.active = true;
}
notify && this.change.next(tabTitle);
} }
addTab(tab) { addTab(tab) {

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
import { getChildDebugElement, getChildDebugElementAll, mouseclick } from 'tests/helpers'; import { getChildDebugElement, getChildDebugElementAll } from 'tests/helpers';
import {Component, View} from 'angular2/core'; import {Component, View} from 'angular2/core';
import { import {
@ -16,9 +16,10 @@ describe('Common components', () => {
describe('Tabs Component', () => { describe('Tabs Component', () => {
let builder; let builder;
let component; let component;
let nativeElement;
let childDebugEls; let childDebugEls;
let debugEl;
let fixture; let fixture;
let hostComponent;
beforeEach(inject([TestComponentBuilder], (tcb) => { beforeEach(inject([TestComponentBuilder], (tcb) => {
builder = tcb; builder = tcb;
@ -26,10 +27,10 @@ describe('Common components', () => {
beforeEach((done) => { beforeEach((done) => {
builder.createAsync(TestApp).then(_fixture => { builder.createAsync(TestApp).then(_fixture => {
fixture = _fixture; fixture = _fixture;
let debugEl = getChildDebugElement(fixture.debugElement, 'tabs'); hostComponent = fixture.debugElement.componentInstance;
debugEl = getChildDebugElement(fixture.debugElement, 'tabs');
childDebugEls = getChildDebugElementAll(debugEl, 'tab'); childDebugEls = getChildDebugElementAll(debugEl, 'tab');
component = debugEl.componentInstance; component = debugEl.componentInstance;
nativeElement = debugEl.nativeElement;
done(); done();
}, err => done.fail(err)); }, err => done.fail(err));
}); });
@ -54,13 +55,82 @@ describe('Common components', () => {
it('should change active tab on click', () => { it('should change active tab on click', () => {
fixture.detectChanges(); fixture.detectChanges();
let headerEls = nativeElement.querySelectorAll('li'); //let headerEls = nativeElement.querySelectorAll('li');
let tabs = childDebugEls.map(debugEl => debugEl.componentInstance); let tabs = childDebugEls.map(debugEl => debugEl.componentInstance);
let [tab1, tab2] = tabs; let [tab1, tab2] = tabs;
mouseclick(headerEls[0]); let tabsInst = debugEl.componentInstance;
tab1.active.should.be.false; tabsInst.selectTab(tab2);
tab2.active.should.be.true; tab1.active.should.be.false();
tab2.active.should.be.true();
});
it('should change tab by title and not emit Event', (done) => {
fixture.detectChanges();
let tabs = childDebugEls.map(debugEl => debugEl.componentInstance);
let [tab1, tab2] = tabs;
let tab2Title = 'Tab2';
let tabsInst = debugEl.componentInstance;
tabsInst.selectyByTitle(tab2Title);
tab1.active.should.be.false();
tab2.active.should.be.true();
setTimeout(() => {
hostComponent.eventLog.should.be.deepEqual([]);
done();
});
});
it('should emit event on tab Change', (done) => {
fixture.detectChanges();
let tabs = childDebugEls.map(debugEl => debugEl.componentInstance);
let tab2 = tabs[1];
let tabsInst = debugEl.componentInstance;
tabsInst.selectTab(tab2, true);
setTimeout(() => {
hostComponent.eventLog.should.be.deepEqual(['Tab2']);
done();
});
});
it('should emit event on tab change by Title with notify true', (done) => {
fixture.detectChanges();
let tab2Title = 'Tab2';
let tabsInst = debugEl.componentInstance;
tabsInst.selectyByTitle(tab2Title, true);
setTimeout(() => {
hostComponent.eventLog.should.be.deepEqual(['Tab2']);
done();
});
});
it('should not emit event on tab change with notify false', (done) => {
fixture.detectChanges();
let tabs = childDebugEls.map(debugEl => debugEl.componentInstance);
let tab2 = tabs[1];
let tabsInst = debugEl.componentInstance;
tabsInst.selectTab(tab2, false);
setTimeout(() => {
hostComponent.eventLog.should.be.deepEqual([]);
done();
});
});
it('should leave current tab active if selectyByTitle non existing title', () => {
fixture.detectChanges();
let tabs = childDebugEls.map(debugEl => debugEl.componentInstance);
let [tab1, tab2] = tabs;
let tabsInst = debugEl.componentInstance;
tabsInst.selectyByTitle('badTitle');
tab1.active.should.be.true();
tab2.active.should.be.false();
}); });
}); });
}); });
@ -71,10 +141,16 @@ describe('Common components', () => {
@View({ @View({
directives: [Tabs, Tab], directives: [Tabs, Tab],
template: template:
`<tabs> `<tabs (change)="onEvent($event)">
<tab tabTitle="Tab1" tabStatus="test">Test</tab> <tab tabTitle="Tab1" tabStatus="test">Test</tab>
<tab tabTitle="Tab2" tabStatus="test">Test</tab> <tab tabTitle="Tab2" tabStatus="test">Test</tab>
</tabs>` </tabs>`
}) })
class TestApp { class TestApp {
constructor() {
this.eventLog = [];
}
onEvent(event) {
this.eventLog.push(event);
}
} }

View File

@ -1,6 +1,6 @@
<header *ngIf="data.bodySchemaPtr || data.samples.length"> Request samples </header> <header *ngIf="data.bodySchemaPtr || data.samples.length"> Request samples </header>
<schema-sample *ngIf="!data.samples.length" [pointer]="data.bodySchemaPtr"> </schema-sample> <schema-sample *ngIf="!data.samples.length" [pointer]="data.bodySchemaPtr"> </schema-sample>
<tabs *ngIf="data.samples.length"> <tabs *ngIf="data.samples.length" (change)=changeLangNotify($event)>
<tab tabTitle="JSON"> <tab tabTitle="JSON">
<schema-sample [pointer]="data.bodySchemaPtr"> </schema-sample> <schema-sample [pointer]="data.bodySchemaPtr"> </schema-sample>
</tab> </tab>

View File

@ -6,17 +6,38 @@ import {Tabs, Tab} from '../../common/components/Tabs/tabs';
import SchemaSample from '../SchemaSample/schema-sample'; import SchemaSample from '../SchemaSample/schema-sample';
import {PrismPipe} from '../../utils/pipes'; import {PrismPipe} from '../../utils/pipes';
import {ViewChildren, QueryList, ChangeDetectorRef, ChangeDetectionStrategy} from 'angular2/core';
import {redocEvents} from '../../events';
@RedocComponent({ @RedocComponent({
selector: 'request-samples', selector: 'request-samples',
templateUrl: './lib/components/RequestSamples/request-samples.html', templateUrl: './lib/components/RequestSamples/request-samples.html',
styleUrls: ['./lib/components/RequestSamples/request-samples.css'], styleUrls: ['./lib/components/RequestSamples/request-samples.css'],
directives: [SchemaSample, Tabs, Tab], directives: [SchemaSample, Tabs, Tab],
inputs: ['bodySchemaPtr'], inputs: ['bodySchemaPtr'],
pipes: [PrismPipe] pipes: [PrismPipe],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export default class RequestSamples extends BaseComponent { export default class RequestSamples extends BaseComponent {
constructor(schemaMgr) { constructor(schemaMgr, tabs, changeDetector) {
super(schemaMgr); super(schemaMgr);
tabs.changes.subscribe(_ => {
this.tabs = tabs.first;
this.subscribeForEvents(_);
});
this.changeDetector = changeDetector;
}
changeLangNotify(lang) {
redocEvents.samplesLanguageChanged.next(lang);
}
subscribeForEvents() {
if (!this.tabs) return;
redocEvents.samplesLanguageChanged.subscribe((sampleLang) => {
this.tabs.selectyByTitle(sampleLang);
this.changeDetector.markForCheck();
});
} }
prepareModel() { prepareModel() {
@ -25,3 +46,5 @@ export default class RequestSamples extends BaseComponent {
this.data.samples = this.componentSchema['x-code-samples'] || []; this.data.samples = this.componentSchema['x-code-samples'] || [];
} }
} }
RequestSamples.parameters = RequestSamples.parameters.concat([ [new ViewChildren(Tabs), QueryList], [ChangeDetectorRef] ]);

View File

@ -3,6 +3,8 @@
import {EventEmitter} from 'angular2/core'; import {EventEmitter} from 'angular2/core';
var bootsrEmmiter = new EventEmitter(); var bootsrEmmiter = new EventEmitter();
var langChanged = new EventEmitter();
export var redocEvents = { export var redocEvents = {
bootstrapped: bootsrEmmiter bootstrapped: bootsrEmmiter,
samplesLanguageChanged: langChanged
}; };

View File

@ -1,7 +1,7 @@
{ {
"name": "redoc", "name": "redoc",
"description": "Swagger-generated API Reference Documentation", "description": "Swagger-generated API Reference Documentation",
"version": "0.5.0", "version": "0.5.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/Rebilly/ReDoc" "url": "git://github.com/Rebilly/ReDoc"
@ -10,7 +10,7 @@
"scripts": { "scripts": {
"test": "gulp lint && ./build/run_tests.sh", "test": "gulp lint && ./build/run_tests.sh",
"prepublish": "gulp build", "prepublish": "gulp build",
"postinstall": "jspm install", "postinstall": "npm install jspm && jspm install",
"start": "gulp serve", "start": "gulp serve",
"build-dist": "gulp build", "build-dist": "gulp build",
"branch-release": "git reset --hard && branch-release", "branch-release": "git reset --hard && branch-release",

View File

@ -14,7 +14,7 @@
<script> <script>
window.redocError = null; window.redocError = null;
/* init redoc */ /* init redoc */
var url = window.location.search.substr(5) || 'swagger.json'; var url = window.location.search.substr(5) || 'swagger.yml';
Redoc.init(decodeURIComponent(url), {disableLazySchemas: true}).then(function() {}, function(err) { Redoc.init(decodeURIComponent(url), {disableLazySchemas: true}).then(function() {}, function(err) {
window.redocError = err; window.redocError = err;
}); });