Browse Source

Merge branch 'dev' into appname-default

pull/14403/head
Halil İbrahim Kalkan 4 years ago
parent
commit
ffa1e1d606
  1. 18
      docs/en/UI/Angular/Account-Module.md
  2. 10
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs
  3. 10
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs
  4. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs
  5. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/DeveloperApiKeyResult.cs
  6. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/DatabaseManagementSystemChangeStep.cs
  7. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs
  8. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/RemoveUnnecessaryPortsStep.cs
  9. 2
      framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo.Abp.EntityFrameworkCore.PostgreSql.csproj
  10. 32
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionHandingUnitOfWork.cs
  11. 10
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController.cs
  12. 24
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs
  13. 5
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs
  14. 25
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs
  15. 8
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Exception_Rollback_Tests.cs
  16. 8
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs
  17. 2
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json
  18. 2
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json
  19. 19
      modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/RedirectToLogin.razor
  20. 13
      modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs
  21. 17
      modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/WebAssemblyRedirectToLogin.razor
  22. 2
      modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Dockerfile
  23. 5
      npm/ng-packs/packages/account/src/lib/account.module.ts
  24. 47
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts
  25. 1
      npm/ng-packs/packages/account/src/lib/models/config-options.ts
  26. 1
      npm/ng-packs/packages/account/src/lib/tokens/index.ts
  27. 5
      npm/ng-packs/packages/account/src/lib/tokens/re-login-confirmation.token.ts
  28. 1
      npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html
  29. 19
      npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts
  30. 62
      templates/app-nolayers/angular/src/app/home/home.component.spec.ts
  31. 3
      templates/app-nolayers/angular/src/test.ts
  32. 100
      templates/app/angular/src/app/home/home.component.spec.ts
  33. 4
      templates/app/angular/src/test.ts
  34. 20
      templates/module/angular/projects/my-project-name/src/lib/my-project-name.component.spec.ts
  35. 15
      templates/module/angular/projects/my-project-name/src/lib/my-project-name.service.spec.ts

18
docs/en/UI/Angular/Account-Module.md

@ -95,6 +95,24 @@ export class AppRoutingModule {}
Before v4.3, the "My account" link in the current user dropdown on the top bar redirected the user to MVC's profile management page. As of v4.3, if you added the account module to your project, the same link will land on a page in the Angular UI account module instead.
### Personal Info Page Confirm Message
When the user changes their own data on the personal settings tab in My Account, The data can not update the CurrentUser key of Application-Configuration. The information of the user is stored in claims. The only way to apply this information to the CurrentUser of Application-Configuration is user should log out and log in. When the Refresh-Token feature is implemented, it will be fixed. So We've added a confirmation alert.
If you want to disable these warning, You should set `isPersonalSettingsChangedConfirmationActive` false
```js
// app-routing.module.ts
const routes: Routes = [
//...
{
path: 'account',
loadChildren: () => import('@volo/abp.ng.account/public').then(m => m.AccountPublicModule.forLazy({ isPersonalSettingsChangedConfirmationActive:false })),
},
//...
export class AppRoutingModule {}
```
### Security Logs Page [COMMERCIAL]
Before v4.3, the "Security Logs" link in the current user dropdown on the top bar redirected the user to MVC's security logs page. As of v4.3, if you added the account module to your project, the same link will land on a page in the Angular UI account public module instead.

10
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs

@ -98,7 +98,15 @@ public class AbpUowActionFilter : IAsyncActionFilter, ITransientDependency
var currentUow = unitOfWorkManager.Current;
if (currentUow != null)
{
await currentUow.SaveChangesAsync(context.HttpContext.RequestAborted);
try
{
await currentUow.SaveChangesAsync(context.HttpContext.RequestAborted);
}
catch (Exception e)
{
await currentUow.RollbackAsync(context.HttpContext.RequestAborted);
throw;
}
}
}

10
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs

@ -104,7 +104,15 @@ public class AbpUowPageFilter : IAsyncPageFilter, ITransientDependency
var currentUow = unitOfWorkManager.Current;
if (currentUow != null)
{
await currentUow.SaveChangesAsync(context.HttpContext.RequestAborted);
try
{
await currentUow.SaveChangesAsync(context.HttpContext.RequestAborted);
}
catch (Exception e)
{
await currentUow.RollbackAsync(context.HttpContext.RequestAborted);
throw;
}
}
}

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs

@ -108,15 +108,15 @@ public class BundlingService : IBundlingService, ITransientDependency
Parameters = parameters
};
scriptContext.BundleDefinitions.AddIfNotContains(
x => x.Source == "_framework/blazor.webassembly.js",
() => new BundleDefinition { Source = "_framework/blazor.webassembly.js" });
foreach (var bundleDefinition in bundleDefinitions)
{
var contributor = CreateContributorInstance(bundleDefinition.BundleContributorType);
contributor.AddScripts(scriptContext);
}
scriptContext.BundleDefinitions.AddIfNotContains(
x => x.Source == "_framework/blazor.webassembly.js",
() => new BundleDefinition { Source = "_framework/blazor.webassembly.js" });
return scriptContext;
}

1
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/DeveloperApiKeyResult.cs

@ -13,6 +13,7 @@ public class DeveloperApiKeyResult
public string ErrorMessage { get; set; }
public LicenseErrorType? ErrorType { get; set; }
public LicenseType LicenseType { get; set; }
public bool IsTrialLicense { get; set; }
public enum LicenseErrorType
{

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/DatabaseManagementSystemChangeStep.cs

@ -98,7 +98,13 @@ public class DatabaseManagementSystemChangeStep : ProjectBuildPipelineStep
var oldUseMethod = "UseSqlServer";
var efCoreModuleClass = context.Files.First(f => f.Name.EndsWith("EntityFrameworkCoreModule.cs", StringComparison.OrdinalIgnoreCase));
var efCoreModuleClass = context.Files.FirstOrDefault(f => f.Name.EndsWith("EntityFrameworkCoreModule.cs", StringComparison.OrdinalIgnoreCase));
if(efCoreModuleClass == null)
{
return;
}
efCoreModuleClass.ReplaceText(oldUseMethod, newUseMethodForEfModule);
var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith($"{(_hasDbMigrations ? "Migrations" : string.Empty)}DbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase))

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs

@ -120,7 +120,7 @@ public static class FileEntryExtensions
}
}
if ((i + 1 < lines.Length) && (lines[i + 1].Contains("<TEMPLATE-REMOVE")))
if (i < lines.Length - 1 && lines[i+1].Contains("<TEMPLATE-REMOVE"))
{
continue;
}

9
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/RemoveUnnecessaryPortsStep.cs

@ -58,8 +58,13 @@ public class RemoveUnnecessaryPortsStep : ProjectBuildPipelineStep
f.Name.Contains("MyCompanyName.MyProjectName.DbMigrator") && f.Name.EndsWith("appsettings.json"));
var appSettingsJsonObject = JObject.Parse(dbMigratorAppSettings.Content);
var authServerJsonObject = (JObject)appSettingsJsonObject["IdentityServer"] ?? (JObject)appSettingsJsonObject["OpenIddict"];
var clientsJsonObject = (JObject)authServerJsonObject["Clients"] ?? (JObject)authServerJsonObject["Applications"];
var authServerJsonObject = (JObject)appSettingsJsonObject?["IdentityServer"] ?? (JObject)appSettingsJsonObject["OpenIddict"];
var clientsJsonObject = (JObject)authServerJsonObject?["Clients"] ?? (JObject)authServerJsonObject?["Applications"];
if (clientsJsonObject == null)
{
return;
}
if (context.BuildArgs.UiFramework != UiFramework.Blazor)
{

2
framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo.Abp.EntityFrameworkCore.PostgreSql.csproj

@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.0-rc.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.0-rc.2" />
</ItemGroup>
</Project>

32
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionHandingUnitOfWork.cs

@ -0,0 +1,32 @@
using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.Uow;
using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
public class ExceptionHandingUnitOfWork : UnitOfWork
{
public ExceptionHandingUnitOfWork(
IServiceProvider serviceProvider,
IUnitOfWorkEventPublisher unitOfWorkEventPublisher,
IOptions<AbpUnitOfWorkDefaultOptions> options)
: base(serviceProvider, unitOfWorkEventPublisher, options)
{
}
public async override Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
if (ServiceProvider.GetRequiredService<ICurrentUser>().Id == Guid.Empty)
{
throw new AbpDbConcurrencyException();
}
await base.SaveChangesAsync(cancellationToken);
}
}

10
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController.cs

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Authorization;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
@ -26,4 +27,11 @@ public class ExceptionTestController : AbpController
{
throw new AbpAuthorizationException("This is a sample exception!");
}
[HttpGet]
[Route("ExceptionOnUowSaveChange")]
public Task<string> ExceptionOnUowSaveChangeAsync()
{
return Task.FromResult("OK");
}
}

24
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs

@ -3,12 +3,14 @@ using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using NSubstitute;
using Shouldly;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http;
using Volo.Abp.Security.Claims;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
@ -31,6 +33,8 @@ public class ExceptionTestController_Tests : AspNetCoreMvcTestBase
_fakeExceptionSubscriber = Substitute.For<IExceptionSubscriber>();
services.AddSingleton(_fakeExceptionSubscriber);
services.Replace(ServiceDescriptor.Transient<IUnitOfWork, ExceptionHandingUnitOfWork>());
}
[Fact]
@ -93,4 +97,24 @@ public class ExceptionTestController_Tests : AspNetCoreMvcTestBase
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
}
[Fact]
public async Task Should_Handle_Exception_On_Uow_SaveChangeAsync()
{
FakeRequiredService.Claims.AddRange(new[]
{
new Claim(AbpClaimTypes.UserId, Guid.Empty.ToString())
});
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/exception-test/ExceptionOnUowSaveChange", HttpStatusCode.Conflict);
result.Error.ShouldNotBeNull();
result.Error.Message.ShouldBe("The data you have submitted has already changed by another user/client. Please discard the changes you've done and try from the beginning.");
#pragma warning disable 4014
_fakeExceptionSubscriber
.Received()
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
}
}

5
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs

@ -36,4 +36,9 @@ public class ExceptionTestPage : AbpPageModel
{
throw new AbpAuthorizationException("This is a sample exception!");
}
public Task<JsonResult> OnGetExceptionOnUowSaveChangeAsync()
{
return Task.FromResult(new JsonResult("OK"));
}
}

25
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs

@ -3,12 +3,14 @@ using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using NSubstitute;
using Shouldly;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http;
using Volo.Abp.Security.Claims;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
@ -31,6 +33,8 @@ public class ExceptionTestPage_Tests : AspNetCoreMvcTestBase
_fakeExceptionSubscriber = Substitute.For<IExceptionSubscriber>();
services.AddSingleton(_fakeExceptionSubscriber);
services.Replace(ServiceDescriptor.Transient<IUnitOfWork, ExceptionHandingUnitOfWork>());
}
[Fact]
@ -140,4 +144,25 @@ public class ExceptionTestPage_Tests : AspNetCoreMvcTestBase
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
}
[Fact]
public async Task Should_Handle_Exception_On_Uow_SaveChangeAsync()
{
_fakeRequiredService.Claims.AddRange(new[]
{
new Claim(AbpClaimTypes.UserId, Guid.Empty.ToString())
});
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/exception-test/ExceptionOnUowSaveChange", HttpStatusCode.Conflict);
result.Error.ShouldNotBeNull();
result.Error.Message.ShouldBe("The data you have submitted has already changed by another user/client. Please discard the changes you've done and try from the beginning.");
#pragma warning disable 4014
_fakeExceptionSubscriber
.Received()
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
}
}

8
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Exception_Rollback_Tests.cs

@ -2,15 +2,23 @@
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Shouldly;
using Volo.Abp.Http;
using Volo.Abp.Json;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Uow;
public class UnitOfWorkMiddleware_Exception_Rollback_Tests : AspNetCoreMvcTestBase
{
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
services.Replace(ServiceDescriptor.Transient<IUnitOfWork, TestUnitOfWork>());
}
[Fact]
public async Task Should_Rollback_Transaction_For_Handled_Exceptions()
{

8
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs

@ -2,15 +2,23 @@
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Shouldly;
using Volo.Abp.Http;
using Volo.Abp.Json;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Uow;
public class UnitOfWorkPageFilter_Exception_Rollback_Tests : AspNetCoreMvcTestBase
{
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
services.Replace(ServiceDescriptor.Transient<IUnitOfWork, TestUnitOfWork>());
}
[Fact]
public async Task Should_Rollback_Transaction_For_Handled_Exceptions()
{

2
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json

@ -34,6 +34,8 @@
"DisplayName:PhoneNumber": "Phone number",
"PersonalSettings": "Personal settings",
"PersonalSettingsSaved": "Personal settings saved",
"PersonalSettingsChangedConfirmationModalTitle": "Personal info changed",
"PersonalSettingsChangedConfirmationModalDescription": "If you want to apply these changes, you have to login. Do you want to log out?",
"PasswordChanged": "Password changed",
"NewPasswordConfirmFailed": "Please confirm the new password.",
"NewPasswordSameAsOld": "New password must be different from the old one.",

2
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json

@ -34,6 +34,8 @@
"DisplayName:PhoneNumber": "Telefon numarası",
"PersonalSettings": "Kişisel ayarlar",
"PersonalSettingsSaved": "Kişisel ayarlar kaydedildi",
"PersonalSettingsChangedConfirmationModalTitle": "Personal info changed",
"PersonalSettingsChangedConfirmationModalDescription": "If you want to apply these changes, you have to login. Do you want to log out?",
"PasswordChanged": "Şifre değiştirildi",
"NewPasswordConfirmFailed": "Lütfen yeni şifreyi onaylayın.",
"NewPasswordSameAsOld": "Yeni şifre eski şifre ile aynı olamaz.",

19
modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/RedirectToLogin.razor

@ -1,17 +1,10 @@
@inject NavigationManager Navigation
@inject IJSRuntime JSRuntime
@code {
@code {
protected override void OnInitialized()
{
bool isWebAssembly = JSRuntime is IJSInProcessRuntime;
if (isWebAssembly)
{
Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
}
else
{
Navigation.NavigateTo($"account/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}", true);
}
}
}
Navigation.NavigateTo($"account/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}", true);
}
}

13
modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs

@ -18,9 +18,6 @@ public partial class LoginDisplay : IDisposable
[Inject]
public AuthenticationStateProvider AuthenticationStateProvider { get; set; }
[CanBeNull]
protected SignOutSessionStateManager SignOutManager;
protected ApplicationMenu Menu { get; set; }
protected override async Task OnInitializedAsync()
@ -29,8 +26,6 @@ public partial class LoginDisplay : IDisposable
Navigation.LocationChanged += OnLocationChanged;
LazyGetService(ref SignOutManager);
AuthenticationStateProvider.AuthenticationStateChanged +=
AuthenticationStateProviderOnAuthenticationStateChanged;
}
@ -65,12 +60,8 @@ public partial class LoginDisplay : IDisposable
}
}
private async Task BeginSignOut()
private void BeginSignOut()
{
if (SignOutManager != null)
{
await SignOutManager.SetSignOutState();
await NavigateToAsync("authentication/logout");
}
Navigation.NavigateToLogout("authentication/logout");
}
}

17
modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/WebAssemblyRedirectToLogin.razor

@ -0,0 +1,17 @@
@inject NavigationManager Navigation
@using Volo.Abp.DependencyInjection
@using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inherits RedirectToLogin
@attribute [ExposeServices(typeof(RedirectToLogin))]
@attribute [Dependency(ReplaceServices = true)]
@code {
protected override void OnInitialized()
{
Navigation.NavigateToLogin("authentication/login");
}
}

2
modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Dockerfile

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0.0-bullseye-slim AS base
FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim AS base
WORKDIR /app
EXPOSE 80
COPY bin/Release/publish .

5
npm/ng-packs/packages/account/src/lib/account.module.ts

@ -15,6 +15,7 @@ import { accountConfigOptionsFactory } from './utils/factory-utils';
import { AuthenticationFlowGuard } from './guards/authentication-flow.guard';
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
import { RE_LOGIN_CONFIRMATION_TOKEN } from './tokens';
import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions';
import { ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS } from './tokens/extensions.token';
import { AccountExtensionsGuard } from './guards/extensions.guard';
@ -55,6 +56,10 @@ export class AccountModule {
useFactory: accountConfigOptionsFactory,
deps: [ACCOUNT_CONFIG_OPTIONS],
},
{
provide: RE_LOGIN_CONFIRMATION_TOKEN,
useValue: options.isPersonalSettingsChangedConfirmationActive ?? true,
},
{
provide: ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS,
useValue: options.editFormPropContributors,

47
npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts

@ -1,10 +1,12 @@
import { ProfileDto, ProfileService } from '@abp/ng.account.core/proxy';
import { ToasterService } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit, ViewEncapsulation } from '@angular/core';
import { Confirmation, ConfirmationService, ToasterService } from '@abp/ng.theme.shared';
import { Component, Inject, Injector, OnInit, ViewEncapsulation } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { finalize, filter } from 'rxjs/operators';
import { Account } from '../../models/account';
import { ManageProfileStateService } from '../../services/manage-profile.state.service';
import { AuthService } from '@abp/ng.core';
import { RE_LOGIN_CONFIRMATION_TOKEN } from '../../tokens';
import {
EXTENSIONS_IDENTIFIER,
FormPropData,
@ -34,12 +36,17 @@ export class PersonalSettingsComponent
form: UntypedFormGroup;
inProgress: boolean;
private profile: ProfileDto;
constructor(
private fb: UntypedFormBuilder,
private toasterService: ToasterService,
private profileService: ProfileService,
private manageProfileState: ManageProfileStateService,
private readonly authService: AuthService,
private confirmationService: ConfirmationService,
@Inject(RE_LOGIN_CONFIRMATION_TOKEN)
private isPersonalSettingsChangedConfirmationActive: boolean,
protected injector: Injector,
) {}
@ -58,6 +65,7 @@ export class PersonalSettingsComponent
submit() {
if (this.form.invalid) return;
const isLogOutConfirmMessageVisible = this.isLogoutConfirmMessageActive();
this.inProgress = true;
this.profileService
.update(this.form.value)
@ -65,6 +73,39 @@ export class PersonalSettingsComponent
.subscribe(profile => {
this.manageProfileState.setProfile(profile);
this.toasterService.success('AbpAccount::PersonalSettingsSaved', 'Success', { life: 5000 });
if (isLogOutConfirmMessageVisible) {
this.showLogoutConfirmMessage();
}
});
}
isDataSame(oldValue, newValue) {
return Object.entries(oldValue).some(([key, value]) => {
if (key in newValue) {
return value !== newValue[key];
}
return false;
});
}
logoutConfirmation = () => {
this.authService.logout().subscribe();
};
private isLogoutConfirmMessageActive() {
if (!this.isPersonalSettingsChangedConfirmationActive) {
return false;
}
return this.isDataSame(this.profile, this.form.value);
}
private showLogoutConfirmMessage() {
this.confirmationService
.info(
'AbpAccount::PersonalSettingsChangedConfirmationModalDescription',
'AbpAccount::PersonalSettingsChangedConfirmationModalTitle',
)
.pipe(filter(status => status === Confirmation.Status.confirm))
.subscribe(this.logoutConfirmation);
}
}

1
npm/ng-packs/packages/account/src/lib/models/config-options.ts

@ -4,6 +4,7 @@ import { UpdateProfileDto } from '@abp/ng.account.core/proxy';
export interface AccountConfigOptions {
redirectUrl?: string;
isPersonalSettingsChangedConfirmationActive?: boolean;
editFormPropContributors?: AccountEditFormPropContributors;
}
export type AccountEditFormPropContributors = Partial<{

1
npm/ng-packs/packages/account/src/lib/tokens/index.ts

@ -1,2 +1,3 @@
export * from './config-options.token';
export * from './re-login-confirmation.token';
export * from './extensions.token';

5
npm/ng-packs/packages/account/src/lib/tokens/re-login-confirmation.token.ts

@ -0,0 +1,5 @@
import { InjectionToken } from '@angular/core';
export const RE_LOGIN_CONFIRMATION_TOKEN = new InjectionToken<boolean>(
'RE_LOGIN_CONFIRMATION_TOKEN',
);

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

@ -16,7 +16,6 @@
></nz-tree>
<ng-template #treeTemplate let-node>
<div
class="node-wrapper"
[class.selected]="isNodeSelected(node)"
[title]="node.title"
(click)="onSelectedNodeChange(node)"

19
npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts

@ -3,6 +3,7 @@ import {
ContentChild,
EventEmitter,
Input,
OnChanges,
Output,
TemplateRef,
ViewEncapsulation,
@ -24,7 +25,7 @@ export type DropEvent = NzFormatEmitEvent & { pos: number };
],
encapsulation: ViewEncapsulation.None,
})
export class TreeComponent {
export class TreeComponent implements OnChanges {
dropPosition: number;
dropdowns = {} as { [key: string]: NgbDropdown };
@ -44,19 +45,31 @@ export class TreeComponent {
@Input() nodes = [];
@Input() expandedKeys: string[] = [];
@Input() selectedNode: any;
@Input() changeCheckboxWithNode: boolean;
@Input() changedNodeValues = [];
@Input() isNodeSelected = node => this.selectedNode?.id === node.key;
@Input() beforeDrop = (event: NzFormatBeforeDropEvent) => {
this.dropPosition = event.pos;
return of(false);
};
ngOnChanges() {
this.checkedKeys = [...this.changedNodeValues];
}
onSelectedNodeChange(node) {
this.selectedNode = node.origin.entity;
this.selectedNodeChange.emit(node.origin.entity);
if (this.changeCheckboxWithNode) {
this.selectedNodeChange.emit(node);
this.checkedKeys = [...this.changedNodeValues];
this.checkedKeysChange.emit(this.changedNodeValues);
} else {
this.selectedNodeChange.emit(node.origin.entity);
}
}
onCheckboxChange(event) {
this.checkedKeys = [...event.keys];
this.checkedKeys = this.changedNodeValues = [...event.keys];
this.checkedKeysChange.emit(event.keys);
}

62
templates/app-nolayers/angular/src/app/home/home.component.spec.ts

@ -0,0 +1,62 @@
import { CoreTestingModule } from '@abp/ng.core/testing';
import { ThemeBasicTestingModule } from '@abp/ng.theme.basic/testing';
import { ThemeSharedTestingModule } from '@abp/ng.theme.shared/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NgxValidateCoreModule } from '@ngx-validate/core';
import { HomeComponent } from './home.component';
import { OAuthService } from 'angular-oauth2-oidc';
import { AuthService } from '@abp/ng.core';
describe('HomeComponent', () => {
let fixture: ComponentFixture<HomeComponent>;
const mockOAuthService = jasmine.createSpyObj('OAuthService', ['hasValidAccessToken']);
const mockAuthService = jasmine.createSpyObj('AuthService', ['navigateToLogin']);
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [HomeComponent],
imports: [
CoreTestingModule.withConfig(),
ThemeSharedTestingModule.withConfig(),
ThemeBasicTestingModule.withConfig(),
NgxValidateCoreModule,
],
providers: [
/* mock providers here */
{
provide: OAuthService,
useValue: mockOAuthService,
},
{
provide: AuthService,
useValue: mockAuthService,
},
],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
fixture.detectChanges();
});
it('should be initiated', () => {
expect(fixture.componentInstance).toBeTruthy();
});
describe('when login state is true', () => {
beforeAll(() => {
mockOAuthService.hasValidAccessToken.and.returnValue(true);
});
it('hasLoggedIn should be true', () => {
expect(fixture.componentInstance.hasLoggedIn).toBeTrue();
expect(mockOAuthService.hasValidAccessToken).toHaveBeenCalled();
});
it('button should not be exists', () => {
const element = fixture.nativeElement;
const cardTitle = element.querySelector('.card-title');
expect(cardTitle).toBeTruthy();
});
});
});

3
templates/app-nolayers/angular/src/test.ts

@ -1,11 +1,10 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import 'zone.js/testing';
declare const require: {
context(

100
templates/app/angular/src/app/home/home.component.spec.ts

@ -0,0 +1,100 @@
import { CoreTestingModule } from "@abp/ng.core/testing";
import { ThemeBasicTestingModule } from "@abp/ng.theme.basic/testing";
import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing";
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
import { NgxValidateCoreModule } from "@ngx-validate/core";
import { HomeComponent } from "./home.component";
import { OAuthService } from 'angular-oauth2-oidc';
import { AuthService } from '@abp/ng.core';
describe("HomeComponent", () => {
let fixture: ComponentFixture<HomeComponent>;
const mockOAuthService = jasmine.createSpyObj('OAuthService', ['hasValidAccessToken'])
const mockAuthService = jasmine.createSpyObj('AuthService', ['navigateToLogin'])
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [HomeComponent],
imports: [
CoreTestingModule.withConfig(),
ThemeSharedTestingModule.withConfig(),
ThemeBasicTestingModule.withConfig(),
NgxValidateCoreModule,
],
providers: [
/* mock providers here */
{
provide: OAuthService,
useValue: mockOAuthService
},
{
provide: AuthService,
useValue: mockAuthService
}
],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
fixture.detectChanges();
});
it("should be initiated", () => {
expect(fixture.componentInstance).toBeTruthy();
});
describe('when login state is true', () => {
beforeAll(() => {
mockOAuthService.hasValidAccessToken.and.returnValue(true)
});
it("hasLoggedIn should be true", () => {
expect(fixture.componentInstance.hasLoggedIn).toBeTrue();
expect(mockOAuthService.hasValidAccessToken).toHaveBeenCalled()
})
it("button should not be exists", () => {
const element = fixture.nativeElement
const button = element.querySelector('[role="button"]')
expect(button).toBeNull()
})
})
describe('when login state is false', () => {
beforeAll(() => {
mockOAuthService.hasValidAccessToken.and.returnValue(false)
});
it("hasLoggedIn should be false", () => {
expect(fixture.componentInstance.hasLoggedIn).toBeFalse();
expect(mockOAuthService.hasValidAccessToken).toHaveBeenCalled()
})
it("button should be exists", () => {
const element = fixture.nativeElement
const button = element.querySelector('[role="button"]')
expect(button).toBeDefined()
})
describe('when button clicked', () => {
beforeEach(() => {
const element = fixture.nativeElement
const button = element.querySelector('[role="button"]')
button.click()
});
it("navigateToLogin have been called", () => {
expect(mockAuthService.navigateToLogin).toHaveBeenCalled()
})
})
})
});

4
templates/app/angular/src/test.ts

@ -1,11 +1,11 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import 'zone.js/testing';
declare const require: {
context(

20
templates/module/angular/projects/my-project-name/src/lib/my-project-name.component.spec.ts

@ -1,16 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MyProjectNameComponent } from './my-project-name.component';
import { MyProjectNameComponent } from './components/my-project-name.component';
import { MyProjectNameService } from '@my-company-name/my-project-name';
import { of } from 'rxjs';
describe('MyProjectNameComponent', () => {
let component: MyProjectNameComponent;
let fixture: ComponentFixture<MyProjectNameComponent>;
const mockMyProjectNameService = jasmine.createSpyObj('MyProjectNameService', {
sample: of([]),
});
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ MyProjectNameComponent ]
})
.compileComponents();
declarations: [MyProjectNameComponent],
providers: [
{
provide: MyProjectNameService,
useValue: mockMyProjectNameService,
},
],
}).compileComponents();
}));
beforeEach(() => {

15
templates/module/angular/projects/my-project-name/src/lib/my-project-name.service.spec.ts

@ -1,12 +1,19 @@
import { TestBed } from '@angular/core/testing';
import { MyProjectNameService } from './my-project-name.service';
import { MyProjectNameService } from './services/my-project-name.service';
import { RestService } from '@abp/ng.core';
describe('MyProjectNameService', () => {
let service: MyProjectNameService;
const mockRestService = jasmine.createSpyObj('RestService', ['request']);
beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [
{
provide: RestService,
useValue: mockRestService,
},
],
});
service = TestBed.inject(MyProjectNameService);
});

Loading…
Cancel
Save