Browse Source

Merge branch 'dev' of https://github.com/volosoft/abp into dev

pull/1649/head
Yunus Emre Kalkan 7 years ago
parent
commit
02237f964d
  1. 1
      build-all.ps1
  2. 1
      docs/en/Tutorials/Angular/Part-I.md
  3. 176
      docs/en/Tutorials/Angular/Part-II.md
  4. 178
      docs/en/Tutorials/Angular/Part-III.md
  5. BIN
      docs/en/Tutorials/Angular/images/bookstore-test-explorer.png
  6. BIN
      docs/en/Tutorials/Angular/images/bookstore-test-projects-v3.png
  7. 1
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.html
  8. 5
      npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts
  9. 3
      npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.html
  10. 46
      npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts
  11. 2
      samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.Application.Tests/Acme.BookStore.Application.Tests.csproj
  12. 72
      samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.Application.Tests/BookAppService_Tests.cs
  13. 2
      samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.Domain.Tests/Acme.BookStore.Domain.Tests.csproj
  14. 2
      samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.MongoDB.Tests/Acme.BookStore.MongoDB.Tests.csproj
  15. 14
      samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.MongoDB.Tests/MongoDb/BookStoreMongoDbTestModule.cs
  16. 4
      samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.TestBase/Acme.BookStore.TestBase.csproj
  17. 43
      samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.TestBase/BookStoreTestDataSeedContributor.cs
  18. 14
      templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDb/MyProjectNameMongoDbTestModule.cs

1
build-all.ps1

@ -21,7 +21,6 @@ $solutionPaths = (
"modules/client-simulation",
"templates/module/aspnet-core",
"templates/app/aspnet-core",
"samples/MicroserviceDemo",
"abp_io/AbpIoLocalization"
)

1
docs/en/Tutorials/Angular/Part-I.md

@ -8,6 +8,7 @@ This is the first part of the Angular tutorial series. See all parts:
- **Part I: Create the project and a book list page (this tutorial)**
- [Part II: Create, Update and Delete books](Part-II.md)
- [Part III: Integration Tests](Part-III.md)
You can access to the **source code** of the application from the [GitHub repository](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb).

176
docs/en/Tutorials/Angular/Part-II.md

@ -6,6 +6,7 @@ This is the second part of the Angular tutorial series. See all parts:
- [Part I: Create the project and a book list page](Part-I.md)
- **Part II: Create, Update and Delete books (this tutorial)**
- [Part III: Integration Tests](Part-III.md)
You can access to the **source code** of the application from the [GitHub repository](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb).
@ -37,19 +38,19 @@ Open the `books.service.ts` and add a new method, named `create` to perform an H
```typescript
create(body: Books.CreateUpdateBookInput): Observable<Books.Book> {
const request: Rest.Request<Books.CreateUpdateBookInput> = {
return this.rest.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'POST',
url: '/api/app/book',
body,
};
return this.rest.request<Books.CreateUpdateBookInput, Books.Book>(request);
});
}
```
* `rest.request` function gets generic parameters for the types sent to and received from the server. This example sends a `CreateUpdateBookInput` object and receives a `Book` object (you can set `void` for request or return type if not used).
#### State Definitions
Add the `CreateUpdateBook` action to `books.actions.ts` as shown below:
Add the `CreateUpdateBook` action to the `books.actions.ts` as shown below:
```typescript
import { Books } from '../models';
@ -78,7 +79,7 @@ When the `SaveBook` action dispatched, the save method is executed. It call `cre
#### Add a Modal to BookListComponent
Open the `book-list.component.html` and add the `abp-modal` to show/hide the book form.
Open the `book-list.component.html` and add the `abp-modal` to show/hide the modal to create a new book.
```html
<abp-modal [(visible)]="isModalOpen">
@ -98,7 +99,7 @@ Open the `book-list.component.html` and add the `abp-modal` to show/hide the boo
`abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits.
Add a button, labeled `New book` to show the modal.
Add a button, labeled `New book` to show the modal:
```html
<div class="row">
@ -161,8 +162,8 @@ buildForm() {
}
```
- The `group` method of `FormBuilder` creates a `FormGroup`.
- Added `Validators.required` static method that validation of form element.
- The `group` method of `FormBuilder` (`fb`) creates a `FormGroup`.
- Added `Validators.required` static method that validates the related form element.
Modify the `onAdd` method as shown below:
@ -173,7 +174,7 @@ onAdd() {
}
```
### Create the DOM Elements of the Form
#### Create the DOM Elements of the Form
Open `book-list.component.html` and add the form in the body template of the modal.
@ -213,28 +214,32 @@ Open `book-list.component.html` and add the form in the body template of the mod
</ng-template>
```
TODO: Add a short description.
* This template creates a form with Name, Price, Type and Publish date fields.
> We've used [NgBootstrap datepicker](https://ng-bootstrap.github.io/#/components/datepicker/overview) in this component.
Open the `book-list.component.ts` and then add the `bookTypes`.
Open the `book-list.component.ts` and then create an array, named `bookTypeArr`:
```typescript
//...
form: FormGroup;
bookTypeArr = Object.keys(Books.BookType).filter(bookType => typeof this.booksType[bookType] === 'number');
bookTypeArr = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
);
```
The `bookTypes` variable added to generate array from `BookType` enum. The `bookTypes` equals like this:
The `bookTypeArr` contains the fields of the `BookType` enum. Resulting array is shown below:
```js
['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...]
```
### Add the Datepicker Requirements
This array was used in the previous form template (in the `ngFor` loop).
Import `NgbDatepickerModule` to the `books.module.ts`.
#### Datepicker Requirements
You need to import `NgbDatepickerModule` to the `books.module.ts`:
```typescript
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
@ -248,25 +253,39 @@ import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
export class BooksModule {}
```
Open the `book-list.component.html` and then add `providers` as shown below:
Then open the `book-list.component.html` and add `providers` as shown below:
```typescript
@Component({
// ...
styleUrls: ['./book-list.component.scss'],
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookListComponent implements OnInit {
// ...
```
> The `NgbDateAdapter` convert Datepicker value type to `Date` type. See the [datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview) for more details.
> The `NgbDateAdapter` converts Datepicker value to `Date` type. See the [datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview) for more details.
![new-book-form](images/bookstore-new-book-form.png)
### Create a New Book
#### Saving the Book
Add the `save` method to `BookListComponent`
Open the `book-list.component.html` and add an `abp-button` to save the form.
```html
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
Cancel
</button>
<abp-button iconClass="fa fa-check" (click)="save()">Save</abp-button>
</ng-template>
```
This adds a save button to the bottom area of the modal:
![bookstore-new-book-form-v2](images/bookstore-new-book-form-v2.png)
Then define a `save` method in the `BookListComponent`:
```typescript
save() {
@ -281,56 +300,32 @@ save() {
}
```
TODO: description ??
### Updating An Existing Book
Then, open the `book-list.component.html` and add the `abp-button` for the run `save` method.
```html
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
Cancel
</button>
<abp-button iconClass="fa fa-check" (click)="save()">Save</abp-button>
</ng-template>
```
Now, You can add a new book.
![bookstore-new-book-form-v2](images/bookstore-new-book-form-v2.png)
### Add HTTP GET and PUT Methods
TODO: Description
#### BooksService
Open the `books.service.ts` and then add the `getById` and `update` methods.
```typescript
getById(id: string): Observable<Books.Book> {
const request: Rest.Request<null> = {
return this.rest.request<null, Books.Book>({
method: 'GET',
url: `/api/app/book/${id}`,
};
return this.rest.request<null, Books.Book>(request);
});
}
update(body: Books.CreateUpdateBookInput, id: string): Observable<Books.Book> {
const request: Rest.Request<Books.CreateUpdateBookInput> = {
return this.rest.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'PUT',
url: `/api/app/book/${id}`,
body,
};
return this.rest.request<Books.CreateUpdateBookInput, Books.Book>(request);
});
}
```
- Added the `getById` method to get the editing book by performing an HTTP request to the related endpoint.
- Added the `update` method to update a book with the `id` by performing an HTTP request to the related endpoint.
TODO: header ??
#### CreateUpdateBook Action
Open the `books.actins.ts` and add `id` parameter.
Open the `books.actins.ts` and add `id` parameter to the `CreateUpdateBook` action:
```typescript
export class CreateUpdateBook {
@ -339,9 +334,7 @@ export class CreateUpdateBook {
}
```
Added `id` parameter to reuse the `BooksSave` while updating and creating a book.
Open `books.state.ts` and then modify the `save` method as show below:
Open `books.state.ts` and modify the `save` method as show below:
```typescript
@Action(CreateUpdateBook)
@ -358,9 +351,9 @@ save({ dispatch }: StateContext<Books.State>, { payload, id }: CreateUpdateBook)
}
```
### Update a Book
#### BookListComponent
Inject `BooksService` dependency by adding it to the `book-list.component.ts` constructor and add variable named `selectedBook`.
Inject `BooksService` dependency by adding it to the `book-list.component.ts` constructor and add a variable named `selectedBook`.
```typescript
import { BooksService } from '../shared/books.service';
@ -373,10 +366,7 @@ constructor(
)
```
- Added `booksService` to get the detail of selected book by `id` before creating the form
- Added `selectedBook` variable to reuse detail of selected book.
Modify the `buildForm` method to reuse the same form while editing a book.
`booksService` is used to get the editing book to prepare the form. Modify the `buildForm` method to reuse the same form while editing a book.
```typescript
buildForm() {
@ -401,9 +391,9 @@ Add the `onEdit` method as shown below:
}
```
- Added `onEdit` method to get selected book detail, build form and then show the modal.
Added `onEdit` method to get the editing book, build the form and show the modal.
Add the `selectedBook` definition to `onAdd` method for reuse same form while adding a new book.
Now, add the `selectedBook` definition to `onAdd` method to reuse the same form while creating a new book:
```typescript
onAdd() {
@ -412,7 +402,7 @@ Add the `selectedBook` definition to `onAdd` method for reuse same form while ad
}
```
Modify the `save` method as shown below:
Modify the `save` method to pass the id of the selected book as shown below:
```typescript
save() {
@ -428,8 +418,6 @@ save() {
}
```
Added the `this.selectedBook.id` property to reuse the `CreateUpdateBook` action.
#### Add "Actions" Dropdown to the Table
Open the `book-list.component.html` and add modify the `p-table` as shown below:
@ -471,15 +459,16 @@ Open the `book-list.component.html` and add modify the `p-table` as shown belo
</p-table>
```
Actions button added to each row of the table.
* Added a `th` for the "Actions" column.
* Added `button` with `ngbDropdownToggle` to open actions when clicked the button.
> We've used to [NgbDropdown](https://ng-bootstrap.github.io/#/components/dropdown/examples) for the dropdown menu of actions.
The Actions buttons looks like this:
The final UI looks like:
![actions-buttons](images/bookstore-actions-buttons.png)
Update the modal header for reuse the same modal.
Update the modal header to change the title based on the current operation:
```html
<ng-template #abpHeader>
@ -489,26 +478,24 @@ Update the modal header for reuse the same modal.
![actions-buttons](images/bookstore-edit-modal.png)
TODO: description & screenshot ??
### Deleting an Existing Book
### Delete a Book
#### BooksService
Open `books.service.ts` and the the `delete` method for delete a book with the `id` by performing an HTTP request to the related endpoint.
Open `books.service.ts` and add a `delete` method to delete a book with the `id` by performing an HTTP request to the related endpoint:
```typescript
delete(id: string): Observable<null> {
const request: Rest.Request<null> = {
delete(id: string): Observable<void> {
return this.rest.request<void, void>({
method: 'DELETE',
url: `/api/app/book/${id}`,
};
return this.rest.request<null, null>(request);
url: `/api/app/book/${id}`
});
}
```
### State Definitions
#### DeleteBook Action
Add an action named `BooksDelete` to `books.actions.ts`
Add an action named `DeleteBook` to `books.actions.ts`:
```typescript
export class DeleteBook {
@ -517,7 +504,7 @@ export class DeleteBook {
}
```
Then, open the `books.state.ts` and add the `delete` method that will listen to a `CreateUpdateBook` action to create a book
Open the `books.state.ts` and add the `delete` method that will listen to the `DeleteBook` action to delete a book:
```typescript
import { ... , DeleteBook } from '../actions/books.actions';
@ -528,11 +515,12 @@ delete({ dispatch }: StateContext<Books.State>, { id }: DeleteBook) {
}
```
TODO: description??
* Added `DeleteBook` to the import list.
* Uses `bookService` to delete the book.
### Add a Delete Button
#### Add a Delete Button
Open `book-list.component.html` and modify the `ngbDropdownMenu` for add the delete button as shown below:
Open `book-list.component.html` and modify the `ngbDropdownMenu` to add the delete button as shown below:
```html
<div ngbDropdownMenu>
@ -543,13 +531,13 @@ Open `book-list.component.html` and modify the `ngbDropdownMenu` for add the del
</div>
```
The final actions dropdown UI looks like this:
The final actions dropdown UI looks like below:
![bookstore-final-actions-dropdown](images/bookstore-final-actions-dropdown.png)
### Open Confirmation Popup
#### Delete Confirmation Dialog
Open `book-list.component.ts` and inject the `ConfirmationService` for show confirmation popup.
Open `book-list.component.ts` and inject the `ConfirmationService`.
```typescript
import { ConfirmationService } from '@abp/ng.theme.shared';
@ -560,7 +548,9 @@ constructor(
)
```
Add the following method to `BookListComponent`.
> `ConfirmationService` is a simple service provided by ABP framework that internally uses the PrimeNG.
Add a delete method to the `BookListComponent`:
```typescript
import { ... , DeleteBook } from '../../store/actions';
@ -577,10 +567,10 @@ delete(id: string, name: string) {
}
```
The `delete` method shows confirmation popup and listens to them. When close the popup, the subscribe block runs. If confirmed this popup, it will dispatch the `DeleteBook` action.
The confirmation popup looks like this:
The `delete` method shows a confirmation popup and subscribes for the user response. `DeleteBook` action dispatched only if user clicks to the `Yes` button. The confirmation popup looks like below:
![bookstore-confirmation-popup](images/bookstore-confirmation-popup.png)
TODO: End
### Next Part
See the [next part](Part-III.md) of this tutorial.

178
docs/en/Tutorials/Angular/Part-III.md

@ -0,0 +1,178 @@
## ASP.NET Core MVC Tutorial - Part III
### About this Tutorial
This is the third part of the Angular tutorial series. See all parts:
- [Part I: Create the project and a book list page](Part-I.md)
- [Part II: Create, Update and Delete books](Part-II.md)
- **Part III: Integration Tests (this tutorial)**
This part covers the **server side** tests. You can access to the **source code** of the application from the [GitHub repository](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb).
### Test Projects in the Solution
There are multiple test projects in the solution:
![bookstore-test-projects](images/bookstore-test-projects-v3.png)
Each project is used to test the related application project. Test projects use the following libraries for testing:
* [xunit](https://xunit.github.io/) as the main test framework.
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) as an assertion library.
* [NSubstitute](http://nsubstitute.github.io/) as a mocking library.
### Adding Test Data
Startup template contains the `BookStoreTestDataSeedContributor` class in the `Acme.BookStore.TestBase` project that creates some data to run tests on.
Change the `BookStoreTestDataSeedContributor` class as show below:
````C#
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace Acme.BookStore
{
public class BookStoreTestDataSeedContributor
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IGuidGenerator _guidGenerator;
public BookStoreTestDataSeedContributor(
IRepository<Book, Guid> bookRepository,
IGuidGenerator guidGenerator)
{
_bookRepository = bookRepository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
await _bookRepository.InsertAsync(
new Book
{
Id = _guidGenerator.Create(),
Name = "Test book 1",
Type = BookType.Fantastic,
PublishDate = new DateTime(2015, 05, 24),
Price = 21
}
);
await _bookRepository.InsertAsync(
new Book
{
Id = _guidGenerator.Create(),
Name = "Test book 2",
Type = BookType.Science,
PublishDate = new DateTime(2014, 02, 11),
Price = 15
}
);
}
}
}
````
* Injected `IRepository<Book, Guid>` and used it in the `SeedAsync` to create two book entities as the test data.
* Used `IGuidGenerator` service to create GUIDs. While `Guid.NewGuid()` would perfectly work for testing, `IGuidGenerator` has additional features especially important while using real databases (see the [Guid generation document](../../Guid-Generation.md) for more).
### Testing the BookAppService
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project:
````C#
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Xunit;
namespace Acme.BookStore
{
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "Test book 1");
}
}
}
````
* `Should_Get_List_Of_Books` test simply uses `BookAppService.GetListAsync` method to get and check the list of users.
Add a new test that creates a valid new book:
````C#
[Fact]
public async Task Should_Create_A_Valid_Book()
{
//Act
var result = await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "New test book 42",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
````
Add a new test that tries to create an invalid book and fails:
````C#
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
});
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
}
````
* Since the `Name` is empty, ABP throws an `AbpValidationException`.
Open the **Test Explorer Window** (use Test -> Windows -> Test Explorer menu if it is not visible) and **Run All** tests:
![bookstore-appservice-tests](images/bookstore-test-explorer.png)
Congratulations, green icons show that tests have been successfully passed!

BIN
docs/en/Tutorials/Angular/images/bookstore-test-explorer.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/en/Tutorials/Angular/images/bookstore-test-projects-v3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

1
npm/ng-packs/packages/identity/src/lib/components/users/users.component.html

@ -188,6 +188,7 @@
requestURLContainSearchValue="users"
iconClass="fa fa-check"
(click)="save()"
[disabled]="form.invalid"
>{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button
>
</ng-template>

5
npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts

@ -6,7 +6,7 @@ import { filter } from 'rxjs/operators';
@Component({
selector: 'abp-button',
template: `
<button [attr.type]="buttonType" [ngClass]="buttonClass" [disabled]="loading">
<button [attr.type]="buttonType" [ngClass]="buttonClass" [disabled]="loading || disabled">
<i [ngClass]="icon" class="mr-1"></i><ng-content></ng-content>
</button>
`,
@ -24,6 +24,9 @@ export class ButtonComponent implements OnInit {
@Input()
loading: boolean = false;
@Input()
disabled: boolean = false;
@Input()
requestType: string | string[];

3
npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.html

@ -1,6 +1,7 @@
<div
*ngIf="showModal"
(click)="onClickBackdrop()"
(click)="close()"
(keyup.esc)="close()"
id="abp-modal"
tabindex="-1"
class="modal fade {{ modalClass }} d-block show"

46
npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts

@ -9,11 +9,13 @@ import {
Renderer2,
TemplateRef,
ViewChild,
ViewChildren,
} from '@angular/core';
import { fromEvent, Subject, timer } from 'rxjs';
import { debounceTime, filter, take, takeUntil } from 'rxjs/operators';
import { ConfirmationService } from '../../services/confirmation.service';
import { filter, take, takeUntil } from 'rxjs/operators';
import { Toaster } from '../../models/toaster';
import { ConfirmationService } from '../../services/confirmation.service';
import { ButtonComponent } from '../button/button.component';
export type ModalSize = 'sm' | 'md' | 'lg' | 'xl';
@ -49,24 +51,35 @@ export class ModalComponent implements OnDestroy {
this.renderer.addClass(this.modalContent.nativeElement, 'fade-out-top');
setTimeout(() => {
this.setVisible(value);
// this.renderer.removeClass(this.modalContent.nativeElement, 'fade-out-top');
this.ngOnDestroy();
}, ANIMATION_TIMEOUT - 10);
}
}
@Input()
get busy(): boolean {
return this._busy;
}
set busy(value: boolean) {
if (this.abpSubmit && this.abpSubmit instanceof ButtonComponent) {
this.abpSubmit.loading = value;
}
this._busy = value;
}
@Input() centered: boolean = false;
@Input() modalClass: string = '';
@Input() size: ModalSize = 'lg';
@Output() visibleChange = new EventEmitter<boolean>();
@Input() height: number;
@Input() minHeight: number;
@Output() visibleChange = new EventEmitter<boolean>();
@Output() init = new EventEmitter<void>();
@ContentChild('abpHeader', { static: false }) abpHeader: TemplateRef<any>;
@ -77,10 +90,16 @@ export class ModalComponent implements OnDestroy {
@ContentChild('abpClose', { static: false, read: ElementRef }) abpClose: ElementRef<any>;
@ContentChild(ButtonComponent, { static: false, read: ButtonComponent }) abpSubmit: ButtonComponent;
@ViewChild('abpModalContent', { static: false }) modalContent: ElementRef;
@ViewChildren('abp-button') abpButtons;
_visible: boolean = false;
_busy: boolean = false;
showModal: boolean = false;
isOpenConfirmation: boolean = false;
@ -108,16 +127,6 @@ export class ModalComponent implements OnDestroy {
}
listen() {
fromEvent(document, 'keyup')
.pipe(
takeUntil(this.destroy$),
debounceTime(250),
filter((key: KeyboardEvent) => key && key.code === 'Escape' && this.closable),
)
.subscribe(_ => {
this.close();
});
setTimeout(() => {
if (!this.abpClose) return;
@ -129,10 +138,13 @@ export class ModalComponent implements OnDestroy {
.subscribe(() => this.close());
}, 0);
window.location.hash = '#abp-modal';
this.init.emit();
}
close() {
if (!this.closable || this.busy) return;
const nodes = getFlatNodes(
(this.modalContent.nativeElement.querySelector('#abp-modal-body') as HTMLElement).childNodes,
);
@ -155,10 +167,8 @@ export class ModalComponent implements OnDestroy {
} else {
this.visible = false;
}
}
onClickBackdrop() {
this.close();
window.location.hash = 'body';
}
}

2
samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.Application.Tests/Acme.BookStore.Application.Tests.csproj

@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
</ItemGroup>
</Project>

72
samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.Application.Tests/BookAppService_Tests.cs

@ -0,0 +1,72 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Xunit;
namespace Acme.BookStore
{
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "Test book 1");
}
[Fact]
public async Task Should_Create_A_Valid_Book()
{
//Act
var result = await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "New test book 42",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
});
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
}
}
}

2
samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.Domain.Tests/Acme.BookStore.Domain.Tests.csproj

@ -12,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
</ItemGroup>
</Project>

2
samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.MongoDB.Tests/Acme.BookStore.MongoDB.Tests.csproj

@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Mongo2Go" Version="2.2.11" />
</ItemGroup>

14
samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.MongoDB.Tests/MongoDb/BookStoreMongoDbTestModule.cs

@ -1,6 +1,5 @@
using System;
using Mongo2Go;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.Modularity;
@ -12,21 +11,18 @@ namespace Acme.BookStore.MongoDB
)]
public class BookStoreMongoDbTestModule : AbpModule
{
private MongoDbRunner _mongoDbRunner;
private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start();
public override void ConfigureServices(ServiceConfigurationContext context)
{
_mongoDbRunner = MongoDbRunner.Start();
var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') +
"BookStore_" +
Guid.NewGuid().ToString("N");
Configure<DbConnectionOptions>(options =>
{
options.ConnectionStrings.Default = _mongoDbRunner.ConnectionString.EnsureEndsWith('/') + "BookStore";
options.ConnectionStrings.Default = connectionString;
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
_mongoDbRunner.Dispose();
}
}
}

4
samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.TestBase/Acme.BookStore.TestBase.csproj

@ -15,8 +15,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
<PackageReference Include="NSubstitute" Version="4.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NSubstitute" Version="4.2.1" />
<PackageReference Include="Shouldly" Version="3.0.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />

43
samples/BookStore-Angular-MongoDb/aspnet-core/test/Acme.BookStore.TestBase/BookStoreTestDataSeedContributor.cs

@ -1,16 +1,49 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace Acme.BookStore
{
public class BookStoreTestDataSeedContributor : IDataSeedContributor, ITransientDependency
public class BookStoreTestDataSeedContributor
: IDataSeedContributor, ITransientDependency
{
public Task SeedAsync(DataSeedContext context)
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IGuidGenerator _guidGenerator;
public BookStoreTestDataSeedContributor(
IRepository<Book, Guid> bookRepository,
IGuidGenerator guidGenerator)
{
_bookRepository = bookRepository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
/* Seed additional test data... */
await _bookRepository.InsertAsync(
new Book
{
Id = _guidGenerator.Create(),
Name = "Test book 1",
Type = BookType.Fantastic,
PublishDate = new DateTime(2015, 05, 24),
Price = 21
}
);
return Task.CompletedTask;
await _bookRepository.InsertAsync(
new Book
{
Id = _guidGenerator.Create(),
Name = "Test book 2",
Type = BookType.Science,
PublishDate = new DateTime(2014, 02, 11),
Price = 15
}
);
}
}
}

14
templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDb/MyProjectNameMongoDbTestModule.cs

@ -1,6 +1,5 @@
using System;
using Mongo2Go;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.Modularity;
@ -12,21 +11,18 @@ namespace MyCompanyName.MyProjectName.MongoDB
)]
public class MyProjectNameMongoDbTestModule : AbpModule
{
private MongoDbRunner _mongoDbRunner;
private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start();
public override void ConfigureServices(ServiceConfigurationContext context)
{
_mongoDbRunner = MongoDbRunner.Start();
var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') +
"MyProjectName_" +
Guid.NewGuid().ToString("N");
Configure<DbConnectionOptions>(options =>
{
options.ConnectionStrings.Default = _mongoDbRunner.ConnectionString.EnsureEndsWith('/') + "MyProjectName";
options.ConnectionStrings.Default = connectionString;
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
_mongoDbRunner.Dispose();
}
}
}
Loading…
Cancel
Save