();
17 | }
18 |
--------------------------------------------------------------------------------
/src/Web/ViewModels/PaginationInfoViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace Microsoft.eShopWeb.Web.ViewModels;
2 |
3 | public class PaginationInfoViewModel
4 | {
5 | public int TotalItems { get; set; }
6 | public int ItemsPerPage { get; set; }
7 | public int ActualPage { get; set; }
8 | public int TotalPages { get; set; }
9 | public string? Previous { get; set; }
10 | public string? Next { get; set; }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Web/Views/Account/Lockout.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Locked out";
3 | }
4 |
5 |
9 |
--------------------------------------------------------------------------------
/src/Web/Views/Manage/ChangePassword.cshtml:
--------------------------------------------------------------------------------
1 | @model ChangePasswordViewModel
2 | @{
3 | ViewData["Title"] = "Change password";
4 | ViewData.AddActivePage(ManageNavPages.ChangePassword);
5 | }
6 |
7 | @ViewData["Title"]
8 |
9 |
32 |
33 | @section Scripts {
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/Web/Views/Manage/Disable2fa.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Disable two-factor authentication (2FA)";
3 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
4 | }
5 |
6 | @ViewData["Title"]
7 |
8 |
9 |
10 |
11 | This action only disables 2FA.
12 |
13 |
14 | Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
15 | used in an authenticator app you should reset your
16 | authenticator keys.
17 |
18 |
19 |
20 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Web/Views/Manage/GenerateRecoveryCodes.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
3 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
4 | }
5 |
6 | @ViewData["Title"]
7 |
8 |
9 |
10 |
11 | This action generates new recovery codes.
12 |
13 |
14 | If you lose your device and don't have the recovery codes you will lose access to your account.
15 |
16 |
17 | Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
18 | used in an authenticator app you should reset your authenticator keys.
19 |
20 |
21 |
22 |
23 |
26 |
--------------------------------------------------------------------------------
/src/Web/Views/Manage/ManageNavPages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Mvc.Rendering;
3 | using Microsoft.AspNetCore.Mvc.ViewFeatures;
4 |
5 | namespace Microsoft.eShopWeb.Web.Views.Manage;
6 |
7 | public static class ManageNavPages
8 | {
9 | public static string ActivePageKey => "ActivePage";
10 |
11 | public static string Index => "Index";
12 |
13 | public static string ChangePassword => "ChangePassword";
14 |
15 | public static string ExternalLogins => "ExternalLogins";
16 |
17 | public static string TwoFactorAuthentication => "TwoFactorAuthentication";
18 |
19 | public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
20 |
21 | public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
22 |
23 | public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
24 |
25 | public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
26 |
27 | public static string PageNavClass(ViewContext viewContext, string page)
28 | {
29 | var activePage = viewContext.ViewData["ActivePage"] as string;
30 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : string.Empty;
31 | }
32 |
33 | public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Web/Views/Manage/ResetAuthenticator.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Reset authenticator key";
3 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
4 | }
5 |
6 | @ViewData["Title"]
7 |
8 |
9 |
10 | If you reset your authenticator key your authenticator app will not work until you reconfigure it.
11 |
12 |
13 | This process disables 2FA until you verify your authenticator app and will also reset your 2FA recovery codes.
14 | If you do not complete your authenticator app configuration you may lose access to your account.
15 |
16 |
17 |
18 |
21 |
--------------------------------------------------------------------------------
/src/Web/Views/Manage/SetPassword.cshtml:
--------------------------------------------------------------------------------
1 | @model SetPasswordViewModel
2 | @{
3 | ViewData["Title"] = "Set password";
4 | ViewData.AddActivePage(ManageNavPages.ChangePassword);
5 | }
6 |
7 | Set your password
8 |
9 |
10 | You do not have a local username/password for this site. Add a local
11 | account so you can log in without an external login.
12 |
13 |
31 |
32 | @section Scripts {
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/Web/Views/Manage/ShowRecoverCodes.cshtml:
--------------------------------------------------------------------------------
1 | @model ShowRecoveryCodesViewModel
2 | @{
3 | ViewData["Title"] = "Recovery codes";
4 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
5 | }
6 |
7 | @ViewData["Title"]
8 |
9 |
10 |
11 | Put these codes in a safe place.
12 |
13 |
14 | If you lose your device and don't have the recovery codes you will lose access to your account.
15 |
16 |
17 |
18 |
19 | @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
20 | {
21 | @Model.RecoveryCodes[row]
@Model.RecoveryCodes[row + 1]
22 | }
23 |
24 |
25 | © 2021 GitHub, Inc.
--------------------------------------------------------------------------------
/src/Web/Views/Manage/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "/Views/Shared/_Layout.cshtml";
3 | }
4 |
5 |
6 |
Manage your account
7 |
Change your account settings
8 |
9 |
10 |
13 |
14 | @RenderBody()
15 |
16 |
17 |
18 |
19 | @section Scripts {
20 | @RenderSection("Scripts", required: false)
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/src/Web/Views/Manage/_ManageNav.cshtml:
--------------------------------------------------------------------------------
1 | @inject SignInManager SignInManager
2 | @{
3 | var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
4 | }
5 |
6 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Web/Views/Manage/_StatusMessage.cshtml:
--------------------------------------------------------------------------------
1 | @model string
2 |
3 | @if (!String.IsNullOrEmpty(Model))
4 | {
5 | var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
6 |
7 | ×
8 | @Model
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/Web/Views/Manage/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.eShopWeb.Web.Views.Manage
--------------------------------------------------------------------------------
/src/Web/Views/Shared/Components/Basket/Default.cshtml:
--------------------------------------------------------------------------------
1 | @model BasketComponentViewModel
2 |
3 | @{
4 | ViewData["Title"] = "My Basket";
5 | }
6 |
7 |
8 |
9 |
10 |
11 |
12 | @Model.ItemsCount
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Web/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Error";
3 | }
4 |
5 | Error.
6 | An error occurred while processing your request.
7 |
8 | Development Mode
9 |
10 | Swapping to Development environment will display more detailed information about the error that occurred.
11 |
12 |
13 | Development environment should not be enabled in deployed applications , as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development , and restarting the application.
14 |
15 |
--------------------------------------------------------------------------------
/src/Web/Views/Shared/_CookieConsentPartial.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Http.Features
2 |
3 | @{
4 | var consentFeature = Context.Features.Get();
5 | var showBanner = !consentFeature?.CanTrack ?? false;
6 | var cookieString = consentFeature?.CreateConsentCookie();
7 | }
8 |
9 | @if (showBanner)
10 | {
11 |
12 | Use this space to summarize your privacy and cookie use policy.
Learn More .
13 |
14 | Accept
15 |
16 |
17 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/Web/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Web/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.eShopWeb.Web
2 | @using Microsoft.eShopWeb.Web.ViewModels
3 | @using Microsoft.eShopWeb.Web.ViewModels.Account
4 | @using Microsoft.eShopWeb.Web.ViewModels.Manage
5 | @using Microsoft.eShopWeb.Web.Pages
6 | @using Microsoft.AspNetCore.Identity
7 | @using Microsoft.eShopWeb.Infrastructure.Identity
8 | @namespace Microsoft.eShopWeb.Web.Pages
9 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
10 |
--------------------------------------------------------------------------------
/src/Web/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/src/Web/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrls": {
3 | "apiBase": "https://localhost:5099/api/",
4 | "webBase": "https://localhost:44315/"
5 | },
6 | "Logging": {
7 | "LogLevel": {
8 | "Default": "Debug",
9 | "System": "Information",
10 | "Microsoft": "Information"
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Web/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrls": {
3 | "apiBase": "https://localhost:5099/api/",
4 | "webBase": "https://localhost:44315/"
5 | },
6 | "ConnectionStrings": {
7 | "CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;",
8 | "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;"
9 | },
10 | "CatalogBaseUrl": "",
11 | "Logging": {
12 | "IncludeScopes": false,
13 | "LogLevel": {
14 | "Default": "Warning",
15 | "Microsoft": "Warning",
16 | "System": "Warning"
17 | },
18 | "AllowedHosts": "*"
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Web/bundleconfig.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "outputFileName": "wwwroot/css/site.min.css",
4 | "inputFiles": [
5 | "wwwroot/css/app.css",
6 | "wwwroot/css/app.component.css",
7 | "wwwroot/css/shared/components/header/header.css",
8 | "wwwroot/css/shared/components/identity/identity.css",
9 | "wwwroot/css/shared/components/pager/pager.css",
10 | "wwwroot/css/basket/basket.component.css",
11 | "wwwroot/css/basket/basket-status/basket-status.component.css",
12 | "wwwroot/css/catalog/catalog.component.css",
13 | "wwwroot/css/orders/orders.component.css"
14 | ]
15 | },
16 | {
17 | "outputFileName": "wwwroot/js/site.min.js",
18 | "inputFiles": [
19 | "wwwroot/js/site.js"
20 | ],
21 | "minify": {
22 | "enabled": true,
23 | "renameLocals": true
24 | },
25 | "sourceMap": false
26 | }
27 | ]
--------------------------------------------------------------------------------
/src/Web/compilerconfig.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "outputFile": "wwwroot/css/shared/components/header/header.css",
4 | "inputFile": "wwwroot/css/shared/components/header/header.scss"
5 | },
6 | {
7 | "outputFile": "wwwroot/css/orders/orders.component.css",
8 | "inputFile": "wwwroot/css/orders/orders.component.scss"
9 | },
10 | {
11 | "outputFile": "wwwroot/css/catalog/catalog.component.css",
12 | "inputFile": "wwwroot/css/catalog/catalog.component.scss"
13 | },
14 | {
15 | "outputFile": "wwwroot/css/basket/basket.component.css",
16 | "inputFile": "wwwroot/css/basket/basket.component.scss"
17 | },
18 | {
19 | "outputFile": "wwwroot/css/basket/basket-status/basket-status.component.css",
20 | "inputFile": "wwwroot/css/basket/basket-status/basket-status.component.scss"
21 | },
22 | {
23 | "outputFile": "wwwroot/css/app.component.css",
24 | "inputFile": "wwwroot/css/app.component.scss"
25 | },
26 | {
27 | "outputFile": "wwwroot/css/_variables.css",
28 | "inputFile": "wwwroot/css/_variables.scss"
29 | },
30 | {
31 | "outputFile": "wwwroot/css/shared/components/pager/pager.css",
32 | "inputFile": "wwwroot/css/shared/components/pager/pager.scss"
33 | }
34 | ]
--------------------------------------------------------------------------------
/src/Web/key-768c1632-cf7b-41a9-bb7a-bff228ae8fba.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2021-12-01T14:37:52.0438755Z
4 | 2021-12-01T14:37:52.0246578Z
5 | 2022-03-01T14:37:52.0246578Z
6 |
7 |
8 |
9 |
10 |
11 |
12 | PF3GdfO7PnvHYvXyD5nxmoQ91pY9qfA0rjRsdXHdUQbE1Mg9Xok2gXLY2zn8XemsySH37UGrGknht8u/PlehWg==
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Web/libman.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0",
3 | "defaultProvider": "cdnjs",
4 | "libraries": [
5 | {
6 | "library": "jquery@3.6.3",
7 | "destination": "wwwroot/lib/jquery/"
8 | },
9 | {
10 | "library": "twitter-bootstrap@5.2.3",
11 | "files": [
12 | "css/bootstrap.css",
13 | "css/bootstrap.css.map",
14 | "css/bootstrap.min.css",
15 | "css/bootstrap.min.css.map",
16 | "js/bootstrap.js",
17 | "js/bootstrap.min.js"
18 | ],
19 | "destination": "wwwroot/lib/bootstrap/dist/"
20 | },
21 | {
22 | "library": "jquery-validation-unobtrusive@4.0.0",
23 | "destination": "wwwroot/lib/jquery-validation-unobtrusive/"
24 | },
25 | {
26 | "library": "jquery-validate@1.19.5",
27 | "destination": "wwwroot/lib/jquery-validate/",
28 | "files": [
29 | "jquery.validate.min.js",
30 | "jquery.validate.js"
31 | ]
32 | },
33 | {
34 | "library": "toastr.js@2.1.4",
35 | "destination": "wwwroot/lib/toastr/"
36 | },
37 | {
38 | "library": "aspnet-signalr@1.0.27",
39 | "files": [
40 | "signalr.js",
41 | "signalr.min.js"
42 | ],
43 | "destination": "wwwroot/lib/@aspnet/signalr/dist/browser/"
44 | }
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/_variables.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/_variables.min.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/app.component.css:
--------------------------------------------------------------------------------
1 | .esh-app-footer {
2 | background-color: #000000;
3 | border-top: 1px solid #EEEEEE;
4 | margin-top: 2.5rem;
5 | padding-bottom: 2.5rem;
6 | padding-top: 2.5rem;
7 | width: 100%;
8 | bottom: 0; }
9 | .esh-app-footer-brand {
10 | height: 50px;
11 | width: 230px; }
12 |
13 | .esh-app-header {
14 | margin: 15px; }
15 |
16 | .esh-app-wrapper {
17 | display: flex;
18 | min-height: 100vh;
19 | flex-direction: column;
20 | justify-content: space-between; }
21 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/app.component.min.css:
--------------------------------------------------------------------------------
1 | .esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%;bottom:0;}.esh-app-footer-brand{height:50px;width:230px;}.esh-app-header{margin:15px;}.esh-app-wrapper{display:flex;min-height:100vh;flex-direction:column;justify-content:space-between;}
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/app.component.scss:
--------------------------------------------------------------------------------
1 | @import './_variables.scss';
2 |
3 | .esh-app {
4 | &-footer {
5 | $margin: 2.5rem;
6 | $padding: 2.5rem;
7 |
8 | background-color: $color-background-darker;
9 | border-top: $border-light solid $color-foreground-bright;
10 | margin-top: $margin;
11 | padding-bottom: $padding;
12 | padding-top: $padding;
13 | width: 100%;
14 | bottom: 0;
15 | $height: 50px;
16 |
17 | &-brand {
18 | height: $height;
19 | width: 230px;
20 | }
21 | }
22 |
23 | &-header {
24 | margin: 15px;
25 | }
26 |
27 | &-wrapper {
28 | display: flex;
29 | min-height: 100vh;
30 | flex-direction: column;
31 | justify-content: space-between
32 | }
33 | }
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/app.min.css:
--------------------------------------------------------------------------------
1 | @font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/basket/basket-status/basket-status.component.css:
--------------------------------------------------------------------------------
1 | .esh-basketstatus {
2 | cursor: pointer;
3 | display: inline-block;
4 | float: right;
5 | position: relative;
6 | transition: all 0.35s; }
7 | .esh-basketstatus.is-disabled {
8 | opacity: .5;
9 | pointer-events: none; }
10 | .esh-basketstatus-image {
11 | height: 36px;
12 | margin-top: .5rem; }
13 | .esh-basketstatus-badge {
14 | background-color: #83D01B;
15 | border-radius: 50%;
16 | color: #FFFFFF;
17 | display: block;
18 | height: 1.5rem;
19 | left: 50%;
20 | position: absolute;
21 | text-align: center;
22 | top: 0;
23 | transform: translateX(-38%);
24 | transition: all 0.35s;
25 | width: 1.5rem; }
26 | .esh-basketstatus-badge-inoperative {
27 | background-color: #ff0000;
28 | border-radius: 50%;
29 | color: #FFFFFF;
30 | display: block;
31 | height: 1.5rem;
32 | left: 50%;
33 | position: absolute;
34 | text-align: center;
35 | top: 0;
36 | transform: translateX(-38%);
37 | transition: all 0.35s;
38 | width: 1.5rem; }
39 | .esh-basketstatus:hover .esh-basketstatus-badge {
40 | background-color: transparent;
41 | color: #75b918;
42 | transition: all 0.35s; }
43 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/basket/basket-status/basket-status.component.min.css:
--------------------------------------------------------------------------------
1 | .esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s;}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none;}.esh-basketstatus-image{height:36px;margin-top:.5rem;}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem;}.esh-basketstatus-badge-inoperative{background-color:#f00;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem;}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s;}
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/basket/basket.component.min.css:
--------------------------------------------------------------------------------
1 | .esh-basket{min-height:80vh;}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem;}.esh-basket-titles--clean{padding-bottom:0;padding-top:0;}.esh-basket-title{text-transform:uppercase;}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0;}.esh-basket-items--border:last-of-type{border-color:transparent;}.esh-basket-items-margin-left1{margin-left:1px;}.esh-basket-item{font-size:1rem;font-weight:300;}.esh-basket-item--middle{line-height:8rem;}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem;}}.esh-basket-item--mark{color:#00a69c;}.esh-basket-image{height:8rem;}.esh-basket-input{line-height:1rem;width:100%;}.esh-basket-checkout{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s;}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s;}.esh-basket-checkout:visited{color:#fff;}
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/catalog/pager.css:
--------------------------------------------------------------------------------
1 | .esh-pager-wrapper {
2 | padding-top: 1rem;
3 | text-align: center;
4 | }
5 |
6 | .esh-pager-item-left {
7 | float: left;
8 | }
9 |
10 | .esh-pager-item-right {
11 | float: right;
12 | }
13 |
14 | .esh-pager-item--navigable {
15 | display: inline-block;
16 | cursor: pointer;
17 | }
18 |
19 | .esh-pager-item--navigable.is-disabled {
20 | opacity: 0;
21 | pointer-events: none;
22 | }
23 |
24 | .esh-pager-item--navigable:hover {
25 | color: #83D01B;
26 | }
27 |
28 | @media screen and (max-width: 1280px) {
29 | .esh-pager-item {
30 | font-size: 0.85rem;
31 | }
32 | }
33 |
34 | @media screen and (max-width: 1024px) {
35 | .esh-pager-item {
36 | margin: 0 4vw;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/orders/orders.component.min.css:
--------------------------------------------------------------------------------
1 | .esh-orders{min-height:80vh;overflow-x:hidden;}.esh-orders-header{background-color:#00a69c;height:4rem;}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s;}.esh-orders-back:hover{color:#fff;transition:color .35s;}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem;}.esh-orders-title{text-transform:uppercase;}.esh-orders-items{height:2rem;line-height:2rem;position:relative;}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1;}.esh-orders-item{font-weight:300;}.esh-orders-item--hover{opacity:0;pointer-events:none;}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all;}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s;}.esh-orders-link:hover{color:#75b918;transition:color .35s;}.esh-orders-detail-section{padding-bottom:30px;}.esh-orders-detail-title{font-size:25px;}
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/shared/components/header/header.css:
--------------------------------------------------------------------------------
1 | .esh-header {
2 | background-color: #00A69C;
3 | height: 4rem; }
4 | .esh-header-back {
5 | color: rgba(255, 255, 255, 0.5);
6 | line-height: 4rem;
7 | text-decoration: none;
8 | text-transform: uppercase;
9 | transition: color 0.35s; }
10 | .esh-header-back:hover {
11 | color: #FFFFFF;
12 | transition: color 0.35s; }
13 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/shared/components/header/header.min.css:
--------------------------------------------------------------------------------
1 | .esh-header{background-color:#00a69c;height:4rem;}.esh-header-back{color:rgba(255,255,255,.5);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s;}.esh-header-back:hover{color:#fff;transition:color .35s;}
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/shared/components/header/header.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .esh-header {
4 | $header-height: 4rem;
5 |
6 | background-color: $color-brand;
7 | height: $header-height;
8 |
9 | &-back {
10 | color: rgba($color-foreground-brighter, .5);
11 | line-height: $header-height;
12 | text-decoration: none;
13 | text-transform: uppercase;
14 | transition: color $animation-speed-default;
15 |
16 | &:hover {
17 | color: $color-foreground-brighter;
18 | transition: color $animation-speed-default;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/shared/components/identity/identity.css:
--------------------------------------------------------------------------------
1 | .esh-identity {
2 | line-height: 3rem;
3 | position: relative;
4 | text-align: right;
5 | }
6 |
7 | .esh-identity-section {
8 | display: inline-block;
9 | width: 100%;
10 | }
11 |
12 | .esh-identity-name {
13 | display: inline-block;
14 | }
15 |
16 | .esh-identity-name--upper {
17 | text-transform: uppercase;
18 | }
19 |
20 | @media screen and (max-width: 768px) {
21 | .esh-identity-name {
22 | font-size: 0.85rem;
23 | }
24 | }
25 |
26 | .esh-identity-image {
27 | display: inline-block;
28 | }
29 |
30 | .esh-identity-drop {
31 | background: #FFFFFF;
32 | height: 10px;
33 | width: 10rem;
34 | overflow: hidden;
35 | padding: .5rem;
36 | position: absolute;
37 | right: 0;
38 | top: 2.5rem;
39 | transition: height 0.35s;
40 | }
41 |
42 | .esh-identity:hover .esh-identity-drop {
43 | border: 1px solid #EEEEEE;
44 | height: 14rem;
45 | transition: height 0.35s;
46 | z-index: 10;
47 | }
48 |
49 | .esh-identity-item {
50 | cursor: pointer;
51 | transition: color 0.35s;
52 | }
53 |
54 | .esh-identity-item:hover {
55 | color: #75b918;
56 | transition: color 0.35s;
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/shared/components/identity/identity.min.css:
--------------------------------------------------------------------------------
1 | .esh-identity{line-height:3rem;position:relative;text-align:right;}.esh-identity-section{display:inline-block;width:100%;}.esh-identity-name{display:inline-block;}.esh-identity-name--upper{text-transform:uppercase;}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem;}}.esh-identity-image{display:inline-block;}.esh-identity-drop{background:#fff;height:10px;width:10rem;overflow:hidden;padding:.5rem;position:absolute;right:0;top:2.5rem;transition:height .35s;}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:10rem;transition:height .35s;}.esh-identity-item{cursor:pointer;transition:color .35s;}.esh-identity-item:hover{color:#75b918;transition:color .35s;}
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/shared/components/identity/identity.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .esh-identity {
4 | line-height: 3rem;
5 | position: relative;
6 | text-align: right;
7 |
8 | &-section {
9 | display: inline-block;
10 | width: 100%;
11 | }
12 |
13 | &-name {
14 | display: inline-block;
15 |
16 | &--upper {
17 | text-transform: uppercase;
18 | }
19 |
20 | @media screen and (max-width: $media-screen-s) {
21 | font-size: $font-size-s;
22 | }
23 | }
24 |
25 | &-image {
26 | display: inline-block;
27 | }
28 |
29 | &-drop {
30 | background: $color-background-brighter;
31 | height: 10px;
32 | width: 10rem;
33 | overflow: hidden;
34 | padding: .5rem;
35 | position: absolute;
36 | right: 0;
37 | top: 2.5rem;
38 | transition: height $animation-speed-default;
39 | }
40 |
41 | &:hover &-drop {
42 | border: $border-light solid $color-foreground-bright;
43 | height: 10rem;
44 | transition: height $animation-speed-default;
45 | }
46 |
47 | &-item {
48 | cursor: pointer;
49 | transition: color $animation-speed-default;
50 |
51 | &:hover {
52 | color: $color-secondary-dark;
53 | transition: color $animation-speed-default;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/shared/components/pager/pager.css:
--------------------------------------------------------------------------------
1 | .esh-pager-wrapper {
2 | padding-top: 1rem;
3 | text-align: center; }
4 |
5 | .esh-pager-item {
6 | margin: 0 5vw; }
7 | .esh-pager-item.is-disabled {
8 | opacity: .5;
9 | pointer-events: none; }
10 | .esh-pager-item--navigable {
11 | cursor: pointer;
12 | display: inline-block; }
13 | .esh-pager-item--navigable:hover {
14 | color: #83D01B; }
15 | @media screen and (max-width: 1280px) {
16 | .esh-pager-item {
17 | font-size: 0.85rem; } }
18 | @media screen and (max-width: 1024px) {
19 | .esh-pager-item {
20 | margin: 0 2.5vw; } }
21 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/shared/components/pager/pager.min.css:
--------------------------------------------------------------------------------
1 | .esh-pager-wrapper{padding-top:1rem;text-align:center;}.esh-pager-item{margin:0 5vw;}.esh-pager-item.is-disabled{opacity:.5;pointer-events:none;}.esh-pager-item--navigable{cursor:pointer;display:inline-block;}.esh-pager-item--navigable:hover{color:#83d01b;}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem;}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 2.5vw;}}
--------------------------------------------------------------------------------
/src/Web/wwwroot/css/shared/components/pager/pager.scss:
--------------------------------------------------------------------------------
1 | @import '../../../variables.scss';
2 |
3 | .esh-pager {
4 |
5 | &-wrapper {
6 | padding-top: 1rem;
7 | text-align: center;
8 | }
9 |
10 | &-item {
11 | $margin: 5vw;
12 | margin: 0 $margin;
13 |
14 | &.is-disabled {
15 | opacity: .5;
16 | pointer-events: none;
17 | }
18 |
19 | &--navigable {
20 | cursor: pointer;
21 | display: inline-block;
22 |
23 | &:hover {
24 | color: $color-secondary;
25 | }
26 | }
27 |
28 | @media screen and (max-width: $media-screen-l) {
29 | font-size: $font-size-s;
30 | }
31 |
32 | @media screen and (max-width: $media-screen-m) {
33 | margin: 0 $margin / 2;
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/src/Web/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/src/Web/wwwroot/fonts/Montserrat-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/fonts/Montserrat-Bold.eot
--------------------------------------------------------------------------------
/src/Web/wwwroot/fonts/Montserrat-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/fonts/Montserrat-Bold.ttf
--------------------------------------------------------------------------------
/src/Web/wwwroot/fonts/Montserrat-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/fonts/Montserrat-Bold.woff
--------------------------------------------------------------------------------
/src/Web/wwwroot/fonts/Montserrat-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/fonts/Montserrat-Bold.woff2
--------------------------------------------------------------------------------
/src/Web/wwwroot/fonts/Montserrat-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/fonts/Montserrat-Regular.eot
--------------------------------------------------------------------------------
/src/Web/wwwroot/fonts/Montserrat-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/fonts/Montserrat-Regular.ttf
--------------------------------------------------------------------------------
/src/Web/wwwroot/fonts/Montserrat-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/fonts/Montserrat-Regular.woff
--------------------------------------------------------------------------------
/src/Web/wwwroot/fonts/Montserrat-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/fonts/Montserrat-Regular.woff2
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/arrow-down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/arrow-down.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/brand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/brand.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/cart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/cart.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/logout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/logout.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/main_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/main_banner.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/main_banner_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/main_banner_text.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/my_orders.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/my_orders.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/1.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/10.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/11.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/12.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/2.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/3.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/4.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/5.jpg
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/5.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/6.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/7.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/8.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/9.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/products/eCatalog-item-default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/images/products/eCatalog-item-default.png
--------------------------------------------------------------------------------
/src/Web/wwwroot/images/refresh.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Write your Javascript code.
2 |
--------------------------------------------------------------------------------
/src/Web/wwwroot/js/site.min.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/eShopOnAKS/ce762d8a057a62cc83c38126fac1c13d18f06a9c/src/Web/wwwroot/js/site.min.js
--------------------------------------------------------------------------------
/tests/FunctionalTests/FunctionalTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Microsoft.eShopWeb.FunctionalTests
5 | false
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/tests/FunctionalTests/Web/Controllers/CatalogControllerIndex.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Threading.Tasks;
3 | using Xunit;
4 |
5 | namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;
6 |
7 | [Collection("Sequential")]
8 | public class CatalogControllerIndex : IClassFixture
9 | {
10 | public CatalogControllerIndex(TestApplication factory)
11 | {
12 | Client = factory.CreateClient();
13 | }
14 |
15 | public HttpClient Client { get; }
16 |
17 | [Fact]
18 | public async Task ReturnsHomePageWithProductListing()
19 | {
20 | // Arrange & Act
21 | var response = await Client.GetAsync("/");
22 | response.EnsureSuccessStatusCode();
23 | var stringResponse = await response.Content.ReadAsStringAsync();
24 |
25 | // Assert
26 | Assert.Contains(".NET Bot Black Sweatshirt", stringResponse);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using Microsoft.AspNetCore.Mvc.Testing;
5 | using Xunit;
6 |
7 | namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;
8 |
9 | [Collection("Sequential")]
10 | public class OrderIndexOnGet : IClassFixture
11 | {
12 | public OrderIndexOnGet(TestApplication factory)
13 | {
14 | Client = factory.CreateClient(new WebApplicationFactoryClientOptions
15 | {
16 | AllowAutoRedirect = false
17 | });
18 | }
19 |
20 | public HttpClient Client { get; }
21 |
22 | [Fact]
23 | public async Task ReturnsRedirectGivenAnonymousUser()
24 | {
25 | var response = await Client.GetAsync("/order/my-orders");
26 | var redirectLocation = response.Headers.Location.OriginalString;
27 |
28 | Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
29 | Assert.Contains("/Account/Login", redirectLocation);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/FunctionalTests/Web/Pages/HomePageOnGet.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.eShopWeb.FunctionalTests.Web;
2 | using Xunit;
3 |
4 | namespace Microsoft.eShopWeb.FunctionalTests.WebRazorPages;
5 |
6 | [Collection("Sequential")]
7 | public class HomePageOnGet : IClassFixture
8 | {
9 | public HomePageOnGet(TestApplication factory)
10 | {
11 | Client = factory.CreateClient();
12 | }
13 |
14 | public HttpClient Client { get; }
15 |
16 | [Fact]
17 | public async Task ReturnsHomePageWithProductListing()
18 | {
19 | // Arrange & Act
20 | var response = await Client.GetAsync("/");
21 | response.EnsureSuccessStatusCode();
22 | var stringResponse = await response.Content.ReadAsStringAsync();
23 |
24 | // Assert
25 | Assert.Contains(".NET Bot Black Sweatshirt", stringResponse);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/FunctionalTests/Web/WebPageHelpers.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace Microsoft.eShopWeb.FunctionalTests.Web;
4 |
5 | public static class WebPageHelpers
6 | {
7 | public static string TokenTag = "__RequestVerificationToken";
8 |
9 | public static string GetRequestVerificationToken(string input)
10 | {
11 | string regexpression = @"name=""__RequestVerificationToken"" type=""hidden"" value=""([-A-Za-z0-9+=/\\_]+?)""";
12 | return RegexSearch(regexpression, input);
13 | }
14 |
15 | public static string GetId(string input)
16 | {
17 | string regexpression = @"name=""Items\[0\].Id"" value=""(\d)""";
18 | return RegexSearch(regexpression, input);
19 | }
20 |
21 | private static string RegexSearch(string regexpression, string input)
22 | {
23 | var regex = new Regex(regexpression);
24 | var match = regex.Match(input);
25 | return match.Groups.Values.LastOrDefault().Value;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/IntegrationTests/IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Microsoft.eShopWeb.IntegrationTests
5 | false
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemGetByIdEndpointTest.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.eShopWeb;
2 | using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System.Net;
5 | using System.Threading.Tasks;
6 |
7 | namespace PublicApiIntegrationTests.CatalogItemEndpoints
8 | {
9 | [TestClass]
10 | public class CatalogItemGetByIdEndpointTest
11 | {
12 | [TestMethod]
13 | public async Task ReturnsItemGivenValidId()
14 | {
15 | var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/5");
16 | response.EnsureSuccessStatusCode();
17 | var stringResponse = await response.Content.ReadAsStringAsync();
18 | var model = stringResponse.FromJson();
19 |
20 | Assert.AreEqual(5, model.CatalogItem.Id);
21 | Assert.AreEqual("Roslyn Red Sheet", model.CatalogItem.Name);
22 | }
23 |
24 | [TestMethod]
25 | public async Task ReturnsNotFoundGivenInvalidId()
26 | {
27 | var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/0");
28 |
29 | Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/PublicApiIntegrationTests/ProgramTest.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc.Testing;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System.Net.Http;
4 |
5 | namespace PublicApiIntegrationTests
6 | {
7 | [TestClass]
8 | public class ProgramTest
9 | {
10 | private static WebApplicationFactory _application;
11 |
12 | public static HttpClient NewClient
13 | {
14 | get
15 | {
16 | return _application.CreateClient();
17 | }
18 | }
19 |
20 | [AssemblyInitialize]
21 | public static void AssemblyInitialize(TestContext _)
22 | {
23 | _application = new WebApplicationFactory();
24 |
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/PublicApiIntegrationTests/PublicApiIntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | enable
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Always
16 | true
17 | PreserveNewest
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | all
28 | runtime; build; native; contentfiles; analyzers; buildtransitive
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/tests/PublicApiIntegrationTests/appsettings.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "UseOnlyInMemoryDatabase": true
3 | }
4 |
--------------------------------------------------------------------------------
/tests/UnitTests/ApplicationCore/Entities/BasketTests/BasketRemoveEmptyItems.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
2 | using Xunit;
3 |
4 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Entities.BasketTests;
5 |
6 | public class BasketRemoveEmptyItems
7 | {
8 | private readonly int _testCatalogItemId = 123;
9 | private readonly decimal _testUnitPrice = 1.23m;
10 | private readonly string _buyerId = "Test buyerId";
11 |
12 | [Fact]
13 | public void RemovesEmptyBasketItems()
14 | {
15 | var basket = new Basket(_buyerId);
16 | basket.AddItem(_testCatalogItemId, _testUnitPrice, 0);
17 | basket.RemoveEmptyItems();
18 |
19 | Assert.Equal(0, basket.Items.Count);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/UnitTests/ApplicationCore/Entities/BasketTests/BasketTotalItems.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
2 | using Xunit;
3 |
4 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Entities.BasketTests;
5 |
6 | public class BasketTotalItems
7 | {
8 | private readonly int _testCatalogItemId = 123;
9 | private readonly decimal _testUnitPrice = 1.23m;
10 | private readonly int _testQuantity = 2;
11 | private readonly string _buyerId = "Test buyerId";
12 |
13 | [Fact]
14 | public void ReturnsTotalQuantityWithOneItem()
15 | {
16 | var basket = new Basket(_buyerId);
17 | basket.AddItem(_testCatalogItemId, _testUnitPrice, _testQuantity);
18 |
19 | var result = basket.TotalItems;
20 |
21 | Assert.Equal(_testQuantity, result);
22 | }
23 |
24 | [Fact]
25 | public void ReturnsTotalQuantityWithMultipleItems()
26 | {
27 | var basket = new Basket(_buyerId);
28 | basket.AddItem(_testCatalogItemId, _testUnitPrice, _testQuantity);
29 | basket.AddItem(_testCatalogItemId, _testUnitPrice, _testQuantity*2);
30 |
31 | var result = basket.TotalItems;
32 |
33 | Assert.Equal(_testQuantity*3, result);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/UnitTests/ApplicationCore/Entities/OrderTests/OrderTotal.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
3 | using Microsoft.eShopWeb.UnitTests.Builders;
4 | using Xunit;
5 |
6 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Entities.OrderTests;
7 |
8 | public class OrderTotal
9 | {
10 | private decimal _testUnitPrice = 42m;
11 |
12 | [Fact]
13 | public void IsZeroForNewOrder()
14 | {
15 | var order = new OrderBuilder().WithNoItems();
16 |
17 | Assert.Equal(0, order.Total());
18 | }
19 |
20 | [Fact]
21 | public void IsCorrectGiven1Item()
22 | {
23 | var builder = new OrderBuilder();
24 | var items = new List
25 | {
26 | new OrderItem(builder.TestCatalogItemOrdered, _testUnitPrice, 1)
27 | };
28 | var order = new OrderBuilder().WithItems(items);
29 | Assert.Equal(_testUnitPrice, order.Total());
30 | }
31 |
32 | [Fact]
33 | public void IsCorrectGiven3Items()
34 | {
35 | var builder = new OrderBuilder();
36 | var order = builder.WithDefaultValues();
37 |
38 | Assert.Equal(builder.TestUnitPrice * builder.TestUnits, order.Total());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/UnitTests/ApplicationCore/Extensions/JsonExtensions.cs:
--------------------------------------------------------------------------------
1 | using Xunit;
2 |
3 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Extensions;
4 |
5 | public class JsonExtensions
6 | {
7 | [Fact]
8 | public void CorrectlySerializesAndDeserializesObject()
9 | {
10 | var testParent = new TestParent
11 | {
12 | Id = 7,
13 | Name = "Test name",
14 | Children = new[]
15 | {
16 | new TestChild(),
17 | new TestChild(),
18 | new TestChild()
19 | }
20 | };
21 |
22 | var json = testParent.ToJson();
23 | var result = json.FromJson();
24 | Assert.Equal(testParent, result);
25 | }
26 |
27 | [
28 | Theory,
29 | InlineData("{ \"id\": 9, \"name\": \"Another test\" }", 9, "Another test"),
30 | InlineData("{ \"id\": 3124, \"name\": \"Test Value 1\" }", 3124, "Test Value 1"),
31 | ]
32 | public void CorrectlyDeserializesJson(string json, int expectedId, string expectedName) =>
33 | Assert.Equal(new TestParent { Id = expectedId, Name = expectedName }, json.FromJson());
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/tests/UnitTests/ApplicationCore/Extensions/TestChild.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Extensions;
6 |
7 | [DebuggerDisplay("Id={Id}, Date={Date}")]
8 | public class TestChild : IEquatable
9 | {
10 | public Guid Id { get; set; } = Guid.NewGuid();
11 |
12 | public DateTime Date { get; set; } = DateTime.UtcNow;
13 |
14 | public bool Equals([AllowNull] TestChild other) =>
15 | other?.Date == Date && other?.Id == Id;
16 | }
17 |
--------------------------------------------------------------------------------
/tests/UnitTests/ApplicationCore/Extensions/TestParent.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Extensions;
4 |
5 | public class TestParent : IEquatable
6 | {
7 | public int Id { get; set; }
8 |
9 | public string? Name { get; set; }
10 |
11 | public IEnumerable? Children { get; set; }
12 |
13 | public bool Equals([AllowNull] TestParent other)
14 | {
15 | if (other?.Id == Id && other?.Name == Name)
16 | {
17 | if (Children is null)
18 | {
19 | return other?.Children is null;
20 | }
21 |
22 | return other?.Children?.Zip(Children).All(t => t.First?.Equals(t.Second) ?? false) ?? false;
23 | }
24 |
25 | return false;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/DeleteBasket.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
3 | using Microsoft.eShopWeb.ApplicationCore.Interfaces;
4 | using Microsoft.eShopWeb.ApplicationCore.Services;
5 | using Moq;
6 | using Xunit;
7 |
8 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTests;
9 |
10 | public class DeleteBasket
11 | {
12 | private readonly string _buyerId = "Test buyerId";
13 | private readonly Mock> _mockBasketRepo = new();
14 | private readonly Mock> _mockLogger = new();
15 |
16 | [Fact]
17 | public async Task ShouldInvokeBasketRepositoryDeleteAsyncOnce()
18 | {
19 | var basket = new Basket(_buyerId);
20 | basket.AddItem(1, It.IsAny(), It.IsAny());
21 | basket.AddItem(2, It.IsAny(), It.IsAny());
22 | _mockBasketRepo.Setup(x => x.GetByIdAsync(It.IsAny(), default))
23 | .ReturnsAsync(basket);
24 | var basketService = new BasketService(_mockBasketRepo.Object, _mockLogger.Object);
25 |
26 | await basketService.DeleteBasketAsync(It.IsAny());
27 |
28 | _mockBasketRepo.Verify(x => x.DeleteAsync(It.IsAny(), default), Times.Once);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/UnitTests/ApplicationCore/Specifications/CatalogFilterSpecification.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Microsoft.eShopWeb.ApplicationCore.Entities;
4 | using Xunit;
5 |
6 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Specifications;
7 |
8 | public class CatalogFilterSpecification
9 | {
10 | [Theory]
11 | [InlineData(null, null, 5)]
12 | [InlineData(1, null, 3)]
13 | [InlineData(2, null, 2)]
14 | [InlineData(null, 1, 2)]
15 | [InlineData(null, 3, 1)]
16 | [InlineData(1, 3, 1)]
17 | [InlineData(2, 3, 0)]
18 | public void MatchesExpectedNumberOfItems(int? brandId, int? typeId, int expectedCount)
19 | {
20 | var spec = new eShopWeb.ApplicationCore.Specifications.CatalogFilterSpecification(brandId, typeId);
21 |
22 | var result = spec.Evaluate(GetTestItemCollection()).ToList();
23 |
24 | Assert.Equal(expectedCount, result.Count());
25 | }
26 |
27 | public List GetTestItemCollection()
28 | {
29 | return new List()
30 | {
31 | new CatalogItem(1, 1, "Description", "Name", 0, "FakePath"),
32 | new CatalogItem(2, 1, "Description", "Name", 0, "FakePath"),
33 | new CatalogItem(3, 1, "Description", "Name", 0, "FakePath"),
34 | new CatalogItem(1, 2, "Description", "Name", 0, "FakePath"),
35 | new CatalogItem(2, 2, "Description", "Name", 0, "FakePath"),
36 | };
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/UnitTests/Builders/AddressBuilder.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
2 |
3 | namespace Microsoft.eShopWeb.UnitTests.Builders;
4 |
5 | public class AddressBuilder
6 | {
7 | private Address _address;
8 | public string TestStreet => "123 Main St.";
9 | public string TestCity => "Kent";
10 | public string TestState => "OH";
11 | public string TestCountry => "USA";
12 | public string TestZipCode => "44240";
13 |
14 | public AddressBuilder()
15 | {
16 | _address = WithDefaultValues();
17 | }
18 | public Address Build()
19 | {
20 | return _address;
21 | }
22 | public Address WithDefaultValues()
23 | {
24 | _address = new Address(TestStreet, TestCity, TestState, TestCountry, TestZipCode);
25 | return _address;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/UnitTests/Builders/BasketBuilder.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
2 | using Moq;
3 |
4 | namespace Microsoft.eShopWeb.UnitTests.Builders;
5 |
6 | public class BasketBuilder
7 | {
8 | private Basket _basket;
9 | public string BasketBuyerId => "testbuyerId@test.com";
10 |
11 | public int BasketId => 1;
12 |
13 | public BasketBuilder()
14 | {
15 | _basket = WithNoItems();
16 | }
17 |
18 | public Basket Build()
19 | {
20 | return _basket;
21 | }
22 |
23 | public Basket WithNoItems()
24 | {
25 | var basketMock = new Mock(BasketBuyerId);
26 | basketMock.SetupGet(s => s.Id).Returns(BasketId);
27 |
28 | _basket = basketMock.Object;
29 | return _basket;
30 | }
31 |
32 | public Basket WithOneBasketItem()
33 | {
34 | var basketMock = new Mock(BasketBuyerId);
35 | _basket = basketMock.Object;
36 | _basket.AddItem(2, 3.40m, 4);
37 | return _basket;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/UnitTests/UnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | enable
5 | Microsoft.eShopWeb.UnitTests
6 | false
7 | latest
8 | enable
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/UnitTests/Web/Extensions/CacheHelpersTests/GenerateBrandsCacheKey.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.eShopWeb.Web.Extensions;
2 | using Xunit;
3 |
4 | namespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests;
5 |
6 | public class GenerateBrandsCacheKey
7 | {
8 | [Fact]
9 | public void ReturnsBrandsCacheKey()
10 | {
11 | var result = CacheHelpers.GenerateBrandsCacheKey();
12 |
13 | Assert.Equal("brands", result);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/UnitTests/Web/Extensions/CacheHelpersTests/GenerateCatalogItemCacheKey.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.eShopWeb.Web;
2 | using Microsoft.eShopWeb.Web.Extensions;
3 | using Xunit;
4 |
5 | namespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests;
6 |
7 | public class GenerateCatalogItemCacheKey
8 | {
9 | [Fact]
10 | public void ReturnsCatalogItemCacheKey()
11 | {
12 | var pageIndex = 0;
13 | int? brandId = null;
14 | int? typeId = null;
15 |
16 | var result = CacheHelpers.GenerateCatalogItemCacheKey(pageIndex, Constants.ITEMS_PER_PAGE, brandId, typeId);
17 |
18 | Assert.Equal("items-0-10--", result);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/UnitTests/Web/Extensions/CacheHelpersTests/GenerateTypesCacheKey.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.eShopWeb.Web.Extensions;
2 | using Xunit;
3 |
4 | namespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests;
5 |
6 | public class GenerateTypesCacheKey
7 | {
8 | [Fact]
9 | public void ReturnsTypesCacheKey()
10 | {
11 | var result = CacheHelpers.GenerateTypesCacheKey();
12 |
13 | Assert.Equal("types", result);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------