This commit is contained in:
muenzpraeger 2019-05-29 08:36:20 -07:00
Коммит 2f05f285f6
194 изменённых файлов: 16051 добавлений и 0 удалений

87
.circleci/config.yml Normal file
Просмотреть файл

@ -0,0 +1,87 @@
version: 2
# Defining default values for all jobs
defaults: &defaults
docker:
- image: circleci/node:lts-browsers
jobs:
setup_tooling:
<<: *defaults
steps:
- checkout
# - Install npm dependencies for LWC linting and testing
- run:
name: Install package dependencies
command: |
yarn install
- persist_to_workspace:
# This is an important step. If we don't store the project data (cloned GitHub source and node_modules)
# we'd have to re-run installation for every workflow step.
root: ~/
paths:
- project/*
prettier_verify:
# This verifies Prettier formatting
<<: *defaults
steps:
- attach_workspace:
at: ~/
- run:
name: Verify Prettier formatting
command: |
yarn prettier:verify
lint_lwc:
# This lints Lightning Web Components.
<<: *defaults
steps:
- attach_workspace:
at: ~/
- run:
name: Lint Lightning Web Components
command: |
yarn lint:lwc
unit_test_lwc:
# This unit tests Lightning Web Components.
<<: *defaults
steps:
- attach_workspace:
at: ~/
- run:
name: Unit test Lightning Web Components
command: |
npm run test:unit:coverage -- -- --runInBand
- persist_to_workspace:
# We're saving the generated coverage results (folder 'coverage', a sub-folder to 'project') so
# that we can upload them in another step to Codecov.io.
root: ~/
paths:
- project/*
upload_code_coverage:
<<: *defaults
steps:
- attach_workspace:
at: ~/
- run:
name: Push to Codecov.io
command: |
# Uploading Apex and LWC tests
bash <(curl -s https://codecov.io/bash)
workflows:
version: 2
build_and_test:
jobs:
- setup_tooling
- prettier_verify:
requires:
- setup_tooling
- lint_lwc:
requires:
- prettier_verify
- unit_test_lwc:
requires:
- lint_lwc
- upload_code_coverage:
requires:
- unit_test_lwc

1
.eslintignore Normal file
Просмотреть файл

@ -0,0 +1 @@
src/resources/external/*

8
.eslintrc.json Normal file
Просмотреть файл

@ -0,0 +1,8 @@
{
"extends": ["@salesforce/eslint-config-lwc/recommended"],
"rules": {
"@lwc/lwc/no-async-operation": "warn",
"@lwc/lwc/no-inner-html": "warn",
"@lwc/lwc/no-document-query": "warn"
}
}

33
.github/ISSUE_TEMPLATE/Bug_Report.md поставляемый Normal file
Просмотреть файл

@ -0,0 +1,33 @@
---
name: Bug report
about: Create a report to help us improve this sample application.
---
<!--
NOTICE: Please use the bug report only for reporting bugs on the application itself.
For general bugs with Lightning Web Components Open Source visit the LWC repository on https://github.com/salesforce/lwc.
If you experience issues with `lwc-create-app` or `lwc-services` please visit https://github.com/muenzpraeger/lwc-create-app.
-->
### Summary
_Short summary of what is going on or to provide context_.
### Steps To Reproduce:
1. This is step 1.
1. This is step 2. All steps should start with '1.'
### Expected result
_Describe what should have happened_.
### Actual result
_Describe what actually happened instead_.
### Additional information
_Feel free to attach a screenshot or code snippets_.

24
.github/ISSUE_TEMPLATE/Feature_Request.md поставляемый Normal file
Просмотреть файл

@ -0,0 +1,24 @@
---
name: Feature request
about: Suggest an idea for this project
---
<!--
NOTICE: Please use the bug report only for reporting bugs on the application itself.
For general feature requests about Lightning Web Components Open Source visit the LWC repository on https://github.com/salesforce/lwc.
For feature requests about `lwc-create-app` or `lwc-services` please visit https://github.com/muenzpraeger/lwc-create-app.
-->
**What is missing from the application? Please describe.**
A clear and concise description of what the problem is. Ex. I'd like to see an implementation of [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

3
.github/SUPPORT.md поставляемый Normal file
Просмотреть файл

@ -0,0 +1,3 @@
**Looking for help?**
Check out the [Lightning Web Components Open Source documentation](https://lwc.dev).

27
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,27 @@
# Log files
logs
*.log
*-debug.log
*-error.log
# Standard dist folder
/dist
# Tooling files
node_modules
jsconfig.json
# Temp directory
/tmp
# Jest coverage folder
/coverage
# MacOS system files
.DS_Store
# Windows system files
Thumbs.db
ehthumbs.db
[Dd]esktop.ini
$RECYCLE.BIN/

3
.prettierignore Normal file
Просмотреть файл

@ -0,0 +1,3 @@
coverage/
dist/
src/resources

11
.prettierrc Normal file
Просмотреть файл

@ -0,0 +1,11 @@
{
"trailingComma": "none",
"singleQuote": true,
"tabWidth": 4,
"overrides": [
{
"files": "**/*.html",
"options": { "parser": "lwc" }
}
]
}

105
CODE_OF_CONDUCT.md Normal file
Просмотреть файл

@ -0,0 +1,105 @@
# Salesforce Open Source Community Code of Conduct
## About the Code of Conduct
Equality is a core value at Salesforce. We believe a diverse and inclusive
community fosters innovation and creativity, and are committed to building a
culture where everyone feels included.
Salesforce open-source projects are committed to providing a friendly, safe, and
welcoming environment for all, regardless of gender identity and expression,
sexual orientation, disability, physical appearance, body size, ethnicity, nationality,
race, age, religion, level of experience, education, socioeconomic status, or
other similar personal characteristics.
The goal of this code of conduct is to specify a baseline standard of behavior so
that people with different social values and communication styles can work
together effectively, productively, and respectfully in our open source community.
It also establishes a mechanism for reporting issues and resolving conflicts.
All questions and reports of abusive, harassing, or otherwise unacceptable behavior
in a Salesforce open-source project may be reported by contacting the Salesforce
Open Source Conduct Committee at ossconduct@salesforce.com.
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of gender
identity and expression, sexual orientation, disability, physical appearance,
body size, ethnicity, nationality, race, age, religion, level of experience, education,
socioeconomic status, or other similar personal characteristics.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy toward other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Personal attacks, insulting/derogatory comments, or trolling
- Public or private harassment
- Publishing, or threatening to publish, others' private information—such as
a physical or electronic address—without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
- Advocating for or encouraging any of the above behaviors
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned with this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project email
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the Salesforce Open Source Conduct Committee
at ossconduct@salesforce.com. All complaints will be reviewed and investigated
and will result in a response that is deemed necessary and appropriate to the
circumstances. The committee is obligated to maintain confidentiality with
regard to the reporter of an incident. Further details of specific enforcement
policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership and the Salesforce Open Source Conduct
Committee.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home],
version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html.
It includes adaptions and additions from [Go Community Code of Conduct][golang-coc],
[CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc].
This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us].
[contributor-covenant-home]: https://www.contributor-covenant.org 'https://www.contributor-covenant.org/'
[golang-coc]: https://golang.org/conduct
[cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
[microsoft-coc]: https://opensource.microsoft.com/codeofconduct/
[cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/

46
CONTRIBUTION.md Normal file
Просмотреть файл

@ -0,0 +1,46 @@
## Contributing
1. Familiarize yourself with the codebase.
1. Create a new issue before starting your project so that we can keep track of
what you are trying to add/fix. That way, we can also offer suggestions or
let you know if there is already an effort in progress. We will let you know when you're good to go to start.
1. Fork this repository.
1. The [README](README.md) has details on how to set up your environment.
1. Create a _topic_ branch in your fork based on the correct branch (usually the **master** branch. Note, this step is recommended but technically not required if contributing using a fork.
1. Edit the code in your fork.
1. Sign CLA (see [CLA](#cla) below)
1. Send us a pull request when you are done. We'll review your code, suggest any
needed changes, and merge it in.
### CLA
External contributors will be required to sign a Contributor's License
Agreement. You can do so by going to https://cla.salesforce.com/sign-cla.
## Branches
- We work in `master`.
- Our released (aka. _production_) branch is `master`.
- Our work happens in _topic_ branches (feature and/or bug-fix).
- feature as well as bug-fix branches are based on `master`
- branches _should_ be kept up-to-date using `rebase`
- see below for further merge instructions
### Merging between branches
- We try to limit merge commits as much as possible.
- _Topic_ branches are:
1. based on `master` and will be
1. squash-merged into `master`.
## Pull Requests
- Develop features and bug fixes in _topic_ branches.
- _Topic_ branches can live in forks (external contributors) or within this repository (committers).
\*\* When creating _topic_ branches in this repository please prefix with `<developer-name>/`.
### Merging Pull Requests
- Pull request merging is restricted to squash & merge only.

119
LICENSE.md Normal file
Просмотреть файл

@ -0,0 +1,119 @@
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

1
Procfile Normal file
Просмотреть файл

@ -0,0 +1 @@
web: yarn serve:heroku

26
README.md Normal file
Просмотреть файл

@ -0,0 +1,26 @@
# Lightning Web Components Recipes Open Source
A collection of easy-to-digest code examples for Lightning Web Components Open Source. Each recipe demonstrates how to code a specific task in 30 lines of code or less. A View Source link takes you right to the code in GitHub. From Hello World to data access and third-party libraries, there is a recipe for that!
## Local Development
1. Clone the `lwc-recipes-oss` repository:
```
git clone https://github.com/trailheadapps/lwc-recipes-oss
cd lwc-recipes-oss
```
2. Install the project dependencies using `yarn` (or `npm`, if you prefer that alternatively)
```
yarn install
```
3. Start the app in watch mode.
```
yarn watch
```
4. Enjoy the app!

3
lwc-services.config.js Normal file
Просмотреть файл

@ -0,0 +1,3 @@
module.exports = {
resources: [{ from: 'src/resources', to: 'dist/resources' }]
};

58
package.json Normal file
Просмотреть файл

@ -0,0 +1,58 @@
{
"name": "lwc-recipes-oss",
"version": "0.1.0",
"author": "Salesforce Developer Evangelism",
"bugs": "https://github.com/trailheadapps/lwc-recipes-oss/issues",
"dependencies": {
"chart.js": "^2.8.0",
"d3": "^5.9.2",
"lwc-services": "^1",
"moment": "^2.24.0"
},
"description": "Lightning Web Components Recipes Open Source",
"devDependencies": {
"husky": "^1.3.1",
"lint-staged": "^8.1.5",
"prettier": "^1.17"
},
"engines": {
"node": ">=8.0.0"
},
"homepage": "https://github.com/trailheadapps/lwc-recipes-oss",
"husky": {
"hooks": {
"pre-push": "lint-staged"
}
},
"keywords": [
"lwc"
],
"license": "CC0-1.0",
"lint-staged": {
"**/*.{html,js,json,yaml,yml,md}": [
"prettier --write"
],
"**/modules/**": [
"eslint"
],
"*": [
"git add"
]
},
"repository": "trailheadapps/lwc-recipes-oss",
"scripts": {
"build": "lwc-services build",
"build:production": "lwc-services build --mode=production",
"lint": "eslint ./src/**/*.js",
"prettier": "prettier --write '**/*.{css,html,js,json,md,yaml,yml}'",
"prettier:verify": "prettier --list-different '**/*.{css,html,js,json,md,yaml,yml}'",
"serve": "lwc-services build && lwc-services serve",
"serve:heroku": "lwc-services build --mode=production && lwc-services serve -i 0.0.0.0",
"test:unit": "lwc-services test",
"test:unit:coverage": "lwc-services test --coverage",
"test:unit:debug": "lwc-services test --debug",
"test:unit:watch": "lwc-services test --watch",
"watch": "lwc-services watch",
"watch:production": "lwc-services watch --mode=production"
}
}

134
src/index.html Normal file
Просмотреть файл

@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Lightning Web Components Recipes</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="/resources/css/main.css" />
<link rel="stylesheet" href="/resources/css/normalize.css" />
<meta charset="utf-8" />
<meta name="author" content="Salesforce Developer Evangelism" />
<meta name="description" content="Lightning Web Components Recipes" />
<meta
name="apple-mobile-web-app-title"
content="Lightning Web Components Recipes"
/>
<meta
name="application-name"
content="Lightning Web Components Recipes"
/>
<meta name="msapplication-TileColor" content="#2d89ef" />
<meta name="theme-color" content="#ffffff" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/resources/images/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/resources/images/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/resources/images/favicon-16x16.png"
/>
<link
rel="mask-icon"
href="/resources/images/safari-pinned-tab.svg"
color="#5bbad5"
/>
<link rel="shortcut icon" href="/resources/images/favicon.ico" />
<link rel="icon" href="/resources/images/logo.svg" />
<main class="wrapper">
<a
href="https://github.com/trailheadapps/lwc-recipes-oss"
class="github-corner"
target="_blank"
aria-label="View source on GitHub"
><svg
width="65"
height="65"
viewBox="0 0 250 250"
style="fill:#16325C; color:#fff; position: absolute; top: 0; border: 0; right: 0;"
aria-hidden="true"
>
<path
d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"
></path>
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor"
style="transform-origin: 130px 106px;"
class="octo-arm"
></path>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor"
class="octo-body"
></path></svg
></a>
<header class="header">
<a class="logo" href="/">
<div class="icon-logo">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 64 64"
>
<path fill="none" d="M0 0h64v64H0z" />
<path
fill="#00a1e0"
d="M23 6h22l-8 18h11L20 58l6-26H16l7-26z"
/>
<path
d="M20 60a2 2 0 0 1-1.95-2.45L23.49 34H16a2 2 0 0 1-1.93-2.52l7-26A2 2 0 0 1 23 4h22a2 2 0 0 1 1.83 2.81L40.08 22H48a2 2 0 0 1 1.54 3.27l-28 34A2 2 0 0 1 20 60zm-1.39-30H26a2 2 0 0 1 1.95 2.45l-4.09 17.72L43.76 26H37a2 2 0 0 1-1.83-2.81L41.92 8H24.53z"
fill="#032e61"
/>
<path
d="M26 26a2 2 0 0 1-1.93-2.53l3-11a2 2 0 1 1 3.86 1.05l-3 11A2 2 0 0 1 26 26z"
fill="#fff"
/>
</svg>
</div>
<h1 class="title">Lightning Web Components Recipes</h1>
</a>
<label class="menu-icon" for="menu-btn"
><span class="navicon"></span
></label>
<ul class="menu">
<li>
<a
href="https://lwc.dev/guide/introduction"
target="_blank"
>Guide</a
>
</li>
<li>
<a
href="https://developer.salesforce.com/"
target="_blank"
>Salesforce Developers</a
>
</li>
<li>
<a
href="https://trailhead.salesforce.com"
target="_blank"
>Trailhead</a
>
</li>
</ul>
</header>
<div id="main"></div>
</main>
</head>
</html>

10
src/index.js Normal file
Просмотреть файл

@ -0,0 +1,10 @@
import { createElement, register } from 'lwc';
import { registerWireService } from '@lwc/wire-service';
import App from 'ui/app';
registerWireService(register);
const app = createElement('ui-app', {
is: App
});
// eslint-disable-next-line @lwc/lwc/no-document-query
document.querySelector('#main').appendChild(app);

Просмотреть файл

@ -0,0 +1,50 @@
export const contacts = [
{
Id: '0031700000pJRRSAA4',
Name: 'Amy Taylor',
Title: 'VP of Engineering',
Phone: '4152568563',
Email: 'amy@demo.net',
Picture: '/resources/images/demo/amy_taylor.jpg'
},
{
Id: '0031700000pJRRTAA4',
Name: 'Michael Jones',
Title: 'VP of Sales',
Phone: '4158526633',
Email: 'michael@demo.net',
Picture: '/resources/images/demo/michael_jones.jpg'
},
{
Id: '0031700000pJRRUAA4',
Name: 'Jennifer Wu',
Title: 'CEO',
Phone: '4158521463',
Email: 'jennifer@demo.net',
Picture: '/resources/images/demo/jennifer_wu.jpg'
},
{
Id: '0031700000pJRRVAA4',
Name: 'Anup Gupta',
Title: 'VP of Products',
Phone: '4158526398',
Email: 'anup@demo.net',
Picture: '/resources/images/demo/anup_gupta.jpg'
},
{
Id: '0031700000pJRRWAA4',
Name: 'Caroline Kingsley',
Title: 'VP of Technology',
Phone: '4158753654',
Email: 'caroline@demo.net',
Picture: '/resources/images/demo/caroline_kingsley.jpg'
},
{
Id: '0031700000pJRRXAA4',
Name: 'Jonathan Bradley',
Title: 'VP of Opearations',
Phone: '4158885522',
Email: 'jonathan@demo.net',
Picture: '/resources/images/demo/jonathan_bradley.jpg'
}
];

Просмотреть файл

@ -0,0 +1,14 @@
import { contacts } from 'data/contacts';
export function findContacts(searchKey) {
if (searchKey.length === 0) return;
const results = contacts.filter(
item => item.Name.toLowerCase().indexOf(searchKey) !== -1
);
// eslint-disable-next-line consistent-return
return results;
}
export function getContactList() {
return { data: contacts };
}

Просмотреть файл

@ -0,0 +1,15 @@
import { register, ValueChangedEvent } from '@lwc/wire-service';
import { contacts } from 'data/contacts';
export default function getContactList() {
// eslint-disable-next-line no-unused-vars
return new Promise((resolve, reject) => {
resolve();
});
}
register(getContactList, eventTarget => {
eventTarget.addEventListener('connect', () => {
eventTarget.dispatchEvent(new ValueChangedEvent({ data: contacts }));
});
});

Просмотреть файл

@ -0,0 +1,28 @@
import { createElement } from 'lwc';
import ApiFunction from 'recipe/apiFunction';
describe('recipe-api-function', () => {
it('calls the public function "refresh" on the recipe-clock component', () => {
// Create initial element
const element = createElement('recipe-api-function', {
is: ApiFunction
});
document.body.appendChild(element);
// Query ui-button component element
const clockEl = element.shadowRoot.querySelector('recipe-clock');
clockEl.refresh = jest.fn();
// Query ui-button element
const buttonEl = element.shadowRoot.querySelector('ui-button');
buttonEl.click();
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
// Compare if public function has been called
expect(clockEl.refresh).toHaveBeenCalled();
});
});
});

Просмотреть файл

@ -0,0 +1,18 @@
recipe-clock {
position: relative;
border: solid 1px #ecebea;
border-radius: 4px;
display: block;
padding: 14px 8px 8px 8px;
margin-top: 16px;
}
recipe-clock:before {
content: 'recipe-clock';
color: #dddbda;
position: absolute;
top: -16px;
left: 4px;
background-color: #ffffff;
padding: 0 4px;
}

Просмотреть файл

@ -0,0 +1,13 @@
<template>
<ui-card title="ApiFunction">
<div>
<ui-button label="Refresh Time" onclick={handleRefresh}></ui-button>
<recipe-clock></recipe-clock>
</div>
<recipe-view-source source="recipe/apiFunction" slot="footer">
Parent-to-child communication. Call a public (@api) function in a
child component.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,7 @@
import { LightningElement } from 'lwc';
export default class ApiFunction extends LightningElement {
handleRefresh() {
this.template.querySelector('recipe-clock').refresh();
}
}

Просмотреть файл

@ -0,0 +1,53 @@
import { createElement } from 'lwc';
import ApiProperty from 'recipe/apiProperty';
const PERCENTAGE_DEFAULT = 50;
const PERCENTAGE_CUSTOM = 40;
describe('recipe-api-property', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('renders recipe-chart-bar component with a default percentage value', () => {
// Create initial element
const element = createElement('recipe-api-property', {
is: ApiProperty
});
document.body.appendChild(element);
// Query chart-bar component
const chartBarEl = element.shadowRoot.querySelector('recipe-chart-bar');
expect(chartBarEl).not.toBeNull();
// Validation for default value passed down to child component
expect(chartBarEl.percentage).toBe(PERCENTAGE_DEFAULT);
});
it('changes the value of the recipe-chart-bar child component based on user input', () => {
// Create initial element
const element = createElement('recipe-api-property', {
is: ApiProperty
});
document.body.appendChild(element);
// Select input field for simulating user input
const uiInputEl = element.shadowRoot.querySelector('ui-input');
uiInputEl.value = PERCENTAGE_CUSTOM;
uiInputEl.dispatchEvent(new CustomEvent('change'));
// Query chart-bar component
const chartBarEl = element.shadowRoot.querySelector('recipe-chart-bar');
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
// Query newly set public property on chart-bar component
expect(chartBarEl.percentage).toBe(PERCENTAGE_CUSTOM);
});
});
});

Просмотреть файл

@ -0,0 +1,18 @@
recipe-chart-bar {
position: relative;
border: solid 1px #ecebea;
border-radius: 4px;
display: block;
padding: 10px 2px 2px 2px;
margin-top: 16px;
}
recipe-chart-bar:before {
content: 'recipe-chart-bar';
color: #dddbda;
position: absolute;
top: -16px;
left: 4px;
background-color: #ffffff;
padding: 0 4px;
}

Просмотреть файл

@ -0,0 +1,20 @@
<template>
<ui-card title="ApiProperty">
<div>
<ui-input
label="Percentage"
type="number"
min="0"
max="100"
value={percentage}
onchange={handlePercentageChange}
></ui-input>
<recipe-chart-bar percentage={percentage}></recipe-chart-bar>
</div>
<recipe-view-source source="recipe/apiProperty" slot="footer">
Parent-to-child communication. Pass data to a child component using
its public (@api) properties.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,10 @@
import { LightningElement, track } from 'lwc';
export default class ApiProperty extends LightningElement {
@track percentage = 50;
handlePercentageChange(event) {
const percentage = event.target.value;
this.percentage = percentage <= 100 ? percentage : 100;
}
}

Просмотреть файл

@ -0,0 +1,48 @@
import { createElement } from 'lwc';
import ApiSetterGetter from 'recipe/apiSetterGetter';
describe('recipe-api-setter-getter', () => {
it('creates a new todo item', () => {
const TODO_DESCRIPTION = 'Some ToDo';
// Create initial element
const element = createElement('recipe-api-setter-getter', {
is: ApiSetterGetter
});
document.body.appendChild(element);
// Query ui-input elements
const uiInputEls = element.shadowRoot.querySelectorAll('ui-input');
const todoCountPrevious = element.shadowRoot.querySelector(
'recipe-todo-list'
).todos.length;
// Select input fields for simulating user input
uiInputEls.forEach(el => {
if (el.label === 'Description') {
el.value = TODO_DESCRIPTION;
} else if (el.label === 'Priority') {
el.checked = true;
}
el.dispatchEvent(new CustomEvent('change'));
});
// Select button for simulating click
const buttonEl = element.shadowRoot.querySelector('ui-button');
buttonEl.click();
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
// Compare if tracked property has been assigned a new value.
const todoListEl = element.shadowRoot.querySelector(
'recipe-todo-list'
);
expect(todoListEl.todos.length).toBe(todoCountPrevious + 1);
expect(todoListEl.todos[2].description).toBe(TODO_DESCRIPTION);
expect(todoListEl.todos[2].priority).toBe(true);
});
});
});

Просмотреть файл

@ -0,0 +1,18 @@
recipe-todo-list {
position: relative;
border: solid 1px #ecebea;
border-radius: 4px;
display: block;
padding: 14px 8px 8px 8px;
margin-top: 16px;
}
recipe-todo-list:before {
content: 'recipe-todo-list';
color: #dddbda;
position: absolute;
top: -16px;
left: 4px;
background-color: #ffffff;
padding: 0 4px;
}

Просмотреть файл

@ -0,0 +1,25 @@
<template>
<ui-card title="ApiSetterGetter">
<div>
<ui-input
label="Description"
onchange={handleDescriptionChange}
value={description}
></ui-input>
<ui-input
label="Priority"
type="checkbox"
onchange={handlePriorityChange}
checked={priority}
></ui-input>
<ui-button label="Add Todo" onclick={handleSave}></ui-button>
<recipe-todo-list todos={todos}></recipe-todo-list>
</div>
<recipe-view-source source="recipe/apiSetterGetter" slot="footer">
Parent-to-child communication. Pass data to a child component using
a public (@api) property implemented with a setter and getter, and
apply some logic to the data as the property is being set.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,36 @@
import { LightningElement, track } from 'lwc';
export default class ApiSetterGetter extends LightningElement {
lastTodoId = 2;
@track
todos = [
{ id: 1, description: 'Explore recipes', priority: true },
{ id: 2, description: 'Install Ebikes sample app', priority: false }
];
@track description;
@track priority = false;
handleDescriptionChange(event) {
this.description = event.target.value;
}
handlePriorityChange(event) {
this.priority = event.target.checked;
}
handleSave() {
this.lastTodoId = this.lastTodoId + 1;
// Using immutable data structures. Creating a new array with old and new items instead of mutating the existing array with push()
this.todos = [
...this.todos,
{
id: this.lastTodoId,
description: this.description,
priority: this.priority
}
];
}
}

Просмотреть файл

@ -0,0 +1,38 @@
import { createElement } from 'lwc';
import ChartBar from 'recipe/chartBar';
describe('recipe-chart-bar', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('renders a div with the percentage value as style attribute', () => {
// Create initial element
const element = createElement('recipe-chart-bar', {
is: ChartBar
});
// Set public property
element.percentage = 40;
document.body.appendChild(element);
// Query div for validating computed style attribute value on component init
const divEl = element.shadowRoot.querySelector('div.bar');
expect(divEl).not.toBeNull();
expect(divEl.style._values.width).toBe('40%');
// Set public property
element.percentage = 60;
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
// Query div for validating computed style attribute value on public property change
expect(divEl.style._values.width).toBe('60%');
});
});
});

Просмотреть файл

@ -0,0 +1,17 @@
.container {
overflow: hidden;
color: #f5b041;
display: flex;
margin-top: 6px;
}
.text {
margin-top: 5px;
margin-left: 4px;
}
.bar {
margin-left: 4px;
height: 36px;
background-color: #f5b041;
}

Просмотреть файл

@ -0,0 +1,6 @@
<template>
<div class="container">
<div class="text">{percentage}%</div>
<div class="bar" style={style}></div>
</div>
</template>

Просмотреть файл

@ -0,0 +1,9 @@
import { LightningElement, api } from 'lwc';
export default class ChartBar extends LightningElement {
@api percentage;
get style() {
return `width: ${this.percentage}%`;
}
}

Просмотреть файл

@ -0,0 +1,27 @@
import { createElement } from 'lwc';
import Clock from 'recipe/clock';
describe('recipe-clock', () => {
it('sets current date/time after public function call', () => {
// Create initial element
const element = createElement('recipe-clock', {
is: Clock
});
document.body.appendChild(element);
// Query ui-output element
const uiDateTimeEl = element.shadowRoot.querySelector('ui-output');
const currentDateTimeVal = uiDateTimeEl.value;
// Call public function on element
element.refresh();
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
// Compare if tracked property has been assigned a new value.
expect(uiDateTimeEl.value).not.toBe(currentDateTimeVal);
});
});
});

Просмотреть файл

@ -0,0 +1,3 @@
<template>
<ui-output value={timestamp}> </ui-output>
</template>

Просмотреть файл

@ -0,0 +1,11 @@
import { LightningElement, api, track } from 'lwc';
export default class Clock extends LightningElement {
@api
refresh() {
this.timestamp = new Date().toISOString();
console.log(this.timestamp);
}
@track timestamp = new Date().toISOString();
}

Просмотреть файл

@ -0,0 +1,43 @@
import { createElement } from 'lwc';
import CompositionBasics from 'recipe/compositionBasics';
describe('recipe-composition-basics', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('renders one contact tile', () => {
// Create initial element
const element = createElement('recipe-composition-basics', {
is: CompositionBasics
});
document.body.appendChild(element);
// Select rendered contact tile for length check
const contactTileEls = element.shadowRoot.querySelectorAll(
'recipe-contact-tile'
);
expect(contactTileEls.length).toBe(1);
});
it('renders with contact tile properties set', () => {
const USER_RESULT = 'Amy Taylor';
const TITLE_RESULT = 'VP of Engineering';
// Create initial element
const element = createElement('recipe-composition-basics', {
is: CompositionBasics
});
document.body.appendChild(element);
// Select contact tile for public property check
const contactTileEl = element.shadowRoot.querySelector(
'recipe-contact-tile'
);
expect(contactTileEl.contact.Name).toBe(USER_RESULT);
expect(contactTileEl.contact.Title).toBe(TITLE_RESULT);
});
});

Просмотреть файл

@ -0,0 +1,17 @@
recipe-contact-tile {
position: relative;
border: solid 1px #ecebea;
border-radius: 4px;
display: block;
padding: 0 12px;
}
recipe-contact-tile:before {
content: 'recipe-contact-tile';
color: #dddbda;
position: absolute;
top: -16px;
left: 4px;
background-color: #ffffff;
padding: 0 4px;
}

Просмотреть файл

@ -0,0 +1,12 @@
<template>
<ui-card title="CompositionBasics">
<div>
<recipe-contact-tile contact={contact}></recipe-contact-tile>
</div>
<recipe-view-source source="recipe/compositionBasics" slot="footer">
Nest a child component into a parent component and pass data to the
child component using its public (@api) properties.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,10 @@
import { LightningElement } from 'lwc';
export default class CompositionParent extends LightningElement {
contact = {
Name: 'Amy Taylor',
Title: 'VP of Engineering',
Phone: '4152568563',
Picture: '/resources/images/demo/amy_taylor.jpg'
};
}

Просмотреть файл

@ -0,0 +1,18 @@
recipe-contact-tile {
position: relative;
border: solid 1px #ecebea;
border-radius: 4px;
display: block;
margin: 15px 0;
padding: 14px 8px 8px 8px;
}
recipe-contact-tile:before {
color: #dddbda;
position: absolute;
top: -16px;
left: 4px;
background-color: #ffffff;
padding: 0 4px;
content: 'recipe-contact-tile';
}

Просмотреть файл

@ -0,0 +1,30 @@
<template>
<ui-card title="CompositionContactSearch">
<div>
<ui-input
type="search"
onchange={handleKeyChange}
label="Search"
></ui-input>
<template if:true={contacts}>
<template for:each={contacts} for:item="contact">
<recipe-contact-tile
key={contact.Id}
contact={contact}
></recipe-contact-tile>
</template>
</template>
</div>
<template if:true={error}>
<recipe-error-panel errors={error}></recipe-error-panel>
</template>
<recipe-view-source
source="recipe/compositionContactSearch"
slot="footer"
>
Create an experience component by assembling multiple child
components. Type a few characters in the search bar to experience
the recipe.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,25 @@
import { LightningElement, track } from 'lwc';
import { findContacts } from 'data/simpleProvider';
/** The delay used when debouncing event handlers before a method call. */
const DELAY = 350;
export default class CompositionContactSearch extends LightningElement {
@track contacts;
@track error;
handleKeyChange(event) {
// Debouncing this method: Do not actually invoke the method call as long as this function is
// being called within a delay of DELAY.
window.clearTimeout(this.delayTimeout);
const searchKey = event.target.value;
// eslint-disable-next-line @lwc/lwc/no-async-operation
this.delayTimeout = setTimeout(() => {
try {
this.contacts = findContacts(searchKey);
} catch (e) {
this.error = e;
}
}, DELAY);
}
}

Просмотреть файл

@ -0,0 +1,44 @@
import { createElement } from 'lwc';
import CompositionIteration from 'recipe/compositionIteration';
describe('recipe-composition-iteration', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('renders three contact tiles', () => {
// Create initial element
const element = createElement('recipe-composition-iteration', {
is: CompositionIteration
});
document.body.appendChild(element);
// Select rendered contact tile elements for length check
const contactTileEls = element.shadowRoot.querySelectorAll(
'recipe-contact-tile'
);
expect(contactTileEls.length).toBe(3);
});
it('renders contact tiles that contain specific names as contact tile data', () => {
// Create initial element
const element = createElement('recipe-composition-basics', {
is: CompositionIteration
});
document.body.appendChild(element);
// Select contact tiles for public property check
const CONTACT_LIST_EXPECTED = [
'Amy Taylor',
'Michael Jones',
'Jennifer Wu'
];
const contactTileNames = Array.from(
element.shadowRoot.querySelectorAll('recipe-contact-tile')
).map(contactTile => contactTile.contact.Name);
expect(contactTileNames).toEqual(CONTACT_LIST_EXPECTED);
});
});

Просмотреть файл

@ -0,0 +1,18 @@
recipe-contact-tile {
position: relative;
border: solid 1px #ecebea;
border-radius: 4px;
display: block;
margin: 15px 0;
padding: 0 12px;
}
recipe-contact-tile:before {
content: 'recipe-contact-tile';
color: #dddbda;
position: absolute;
top: -16px;
left: 4px;
background-color: #ffffff;
padding: 0 4px;
}

Просмотреть файл

@ -0,0 +1,17 @@
<template>
<ui-card title="CompositionIteration">
<div>
<template for:each={contacts} for:item="contact">
<recipe-contact-tile
key={contact.Id}
contact={contact}
></recipe-contact-tile>
</template>
</div>
<recipe-view-source source="recipe/compositionIteration" slot="footer">
Loop through an array of items in a template, and nest an instance
of a child component for each item in the array.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,27 @@
import { LightningElement } from 'lwc';
export default class CompositionIteration extends LightningElement {
contacts = [
{
Id: '003171931112854375',
Name: 'Amy Taylor',
Title: 'VP of Engineering',
Phone: '4152568563',
Picture: '/resources/images/demo/amy_taylor.jpg'
},
{
Id: '003192301009134555',
Name: 'Michael Jones',
Title: 'VP of Sales',
Phone: '4158526633',
Picture: '/resources/images/demo/michael_jones.jpg'
},
{
Id: '003848991274589432',
Name: 'Jennifer Wu',
Title: 'CEO',
Phone: '4158521463',
Picture: '/resources/images/demo/jennifer_wu.jpg'
}
];
}

Просмотреть файл

@ -0,0 +1,36 @@
import { createElement } from 'lwc';
import ContactListItem from 'recipe/contactListItem';
describe('recipe-contact-list-item', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('shows contact name and image based on public property', () => {
const CONTACT = {
Id: '0031700000pJRRSAA4',
Name: 'Amy Taylor',
Title: 'VP of Engineering',
Phone: '4152568563',
Email: 'amy@demo.net',
Picture: '/resources/images/demo/amy_taylor.jpg'
};
// Create initial element
const element = createElement('recipe-contact-list-item', {
is: ContactListItem
});
// Set public property
element.contact = CONTACT;
document.body.appendChild(element);
// Select elements for validation
const imgEl = element.shadowRoot.querySelector('img');
expect(imgEl.src).toContain(CONTACT.Picture);
const nameEl = element.shadowRoot.querySelector('p');
expect(nameEl.textContent).toBe(CONTACT.Name);
});
});

Просмотреть файл

@ -0,0 +1,20 @@
:host {
width: 300px;
}
a {
display: flex;
margin-left: 6px;
text-decoration: none;
color: var(--color-text-link);
}
p {
margin: 0 0 8px 6px;
}
img {
width: 30px;
height: 30px;
border-radius: 50%;
}

Просмотреть файл

@ -0,0 +1,6 @@
<template>
<a href="#" onclick={handleClick}>
<img src={contact.Picture} alt="Profile photo" />
<p>{contact.Name}</p>
</a>
</template>

Просмотреть файл

@ -0,0 +1,16 @@
import { LightningElement, api } from 'lwc';
export default class ContactListItem extends LightningElement {
@api contact;
handleClick(event) {
// 1. Prevent default behavior of anchor tag click which is to navigate to the href url
event.preventDefault();
// 2. Read about event best practices at http://lwc.dev/guide/events#pass-data-in-events
const selectEvent = new CustomEvent('select', {
detail: this.contact.Id
});
// 3. Fire the custom event
this.dispatchEvent(selectEvent);
}
}

Просмотреть файл

@ -0,0 +1,36 @@
import { createElement } from 'lwc';
import ContactListItemBubbling from 'recipe/contactListItemBubbling';
describe('recipe-contact-list-item-bubbling', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('shows contact name and image based on public property', () => {
const CONTACT = {
Id: '0031700000pJRRSAA4',
Name: 'Amy Taylor',
Title: 'VP of Engineering',
Phone: '4152568563',
Email: 'amy@demo.net',
Picture: '/resources/images/demo/amy_taylor.jpg'
};
// Create initial element
const element = createElement('recipe-contact-list-item-bubbling', {
is: ContactListItemBubbling
});
// Set public property
element.contact = CONTACT;
document.body.appendChild(element);
// Select elements for validation
const imgEl = element.shadowRoot.querySelector('img');
expect(imgEl.src).toContain(CONTACT.Picture);
const nameEl = element.shadowRoot.querySelector('p');
expect(nameEl.textContent).toBe(CONTACT.Name);
});
});

Просмотреть файл

@ -0,0 +1,20 @@
:host {
width: 300px;
}
a {
display: flex;
margin-left: 6px;
text-decoration: none;
color: var(--color-text-link);
}
p {
margin: 0 0 8px 6px;
}
img {
width: 30px;
height: 30px;
border-radius: 50%;
}

Просмотреть файл

@ -0,0 +1,6 @@
<template>
<a href="#" onclick={handleSelect}>
<img src={contact.Picture} alt="Profile photo" />
<p>{contact.Name}</p>
</a>
</template>

Просмотреть файл

@ -0,0 +1,16 @@
import { LightningElement, api } from 'lwc';
export default class ContactListItemBubbling extends LightningElement {
@api contact;
handleSelect(event) {
// 1. Prevent default behavior of anchor tag click which is to navigate to the href url
event.preventDefault();
// 2. Create a custom event that bubbles. Read about event best practices at https://lwc.dev/guide/events#configure-event-propagation
const selectEvent = new CustomEvent('contactselect', {
bubbles: true
});
// 3. Fire the custom event
this.dispatchEvent(selectEvent);
}
}

Просмотреть файл

@ -0,0 +1,55 @@
import { createElement } from 'lwc';
import ContactTile from 'recipe/contactTile';
const CONTACT_INPUT = {
Id: '0031700000pJRRSAA4',
Name: 'Amy Taylor',
Title: 'VP of Engineering',
Phone: '4152568563',
Email: 'amy@demo.net',
Picture: '/resources/images/demo/amy_taylor.jpg'
};
describe('recipe-contact-tile', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('renders picture, name, title, and phone number based on public property input', () => {
// Create initial element
const element = createElement('recipe-contact-tile', {
is: ContactTile
});
// Set public property
element.contact = CONTACT_INPUT;
document.body.appendChild(element);
// Select elements for validation
const imgEl = element.shadowRoot.querySelector('img');
expect(imgEl.src).toContain(CONTACT_INPUT.Picture);
const detailEls = element.shadowRoot.querySelectorAll('p');
expect(detailEls[0].textContent).toBe(CONTACT_INPUT.Name);
expect(detailEls[1].textContent).toBe(CONTACT_INPUT.Title);
const phoneEl = element.shadowRoot.querySelector('ui-output');
expect(phoneEl.value).toBe(CONTACT_INPUT.Phone);
});
it('renders an informational message if public property is not set', () => {
const MESSAGE = 'No contact data available.';
// Create initial element
const element = createElement('recipe-contact-tile', {
is: ContactTile
});
document.body.appendChild(element);
// Select element for validation
const detailEl = element.shadowRoot.querySelector('p');
expect(detailEl.textContent).toBe(MESSAGE);
});
});

Просмотреть файл

@ -0,0 +1,14 @@
:host {
padding: 16px;
}
img {
margin-top: 20px;
width: 60px;
height: 60px;
border-radius: 50%;
}
p {
margin: 0;
}

Просмотреть файл

@ -0,0 +1,13 @@
<template>
<template if:true={contact}>
<img src={contact.Picture} alt="Profile photo" />
<p>{contact.Name}</p>
<p>{contact.Title}</p>
<p>
<ui-output type="phone" value={contact.Phone}></ui-output>
</p>
</template>
<template if:false={contact}
><p>No contact data available.</p></template
>
</template>

Просмотреть файл

@ -0,0 +1,5 @@
import { LightningElement, api } from 'lwc';
export default class ContactTile extends LightningElement {
@api contact;
}

Просмотреть файл

@ -0,0 +1,78 @@
import { createElement } from 'lwc';
import ErrorPanel from 'recipe/errorPanel';
describe('recipe-error-panel', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('displays a default friendly message', () => {
const MESSAGE = 'Error retrieving data';
// Create initial element
const element = createElement('recipe-error-panel', {
is: ErrorPanel
});
document.body.appendChild(element);
const messageEl = element.shadowRoot.querySelector('p');
expect(messageEl.textContent).toBe(MESSAGE);
});
it('displays a custom friendly message', () => {
const MESSAGE = 'Errors are bad';
// Create initial element
const element = createElement('recipe-error-panel', {
is: ErrorPanel
});
element.friendlyMessage = MESSAGE;
document.body.appendChild(element);
const messageEl = element.shadowRoot.querySelector('p');
expect(messageEl.textContent).toBe(MESSAGE);
});
it('displays no error details when no errors are passed as parameters', () => {
// Create initial element
const element = createElement('recipe-error-panel', {
is: ErrorPanel
});
document.body.appendChild(element);
const inputEl = element.shadowRoot.querySelector('ui-input');
expect(inputEl).toBeNull();
});
it('displays error details when errors are passed as parameters', () => {
const ERROR_MESSAGES_INPUT = [
{ statusText: 'First bad error' },
{ statusText: 'Second bad error' }
];
const ERROR_MESSAGES_OUTPUT = ['First bad error', 'Second bad error'];
// Create initial element
const element = createElement('recipe-error-panel', {
is: ErrorPanel
});
element.errors = ERROR_MESSAGES_INPUT;
document.body.appendChild(element);
const inputEl = element.shadowRoot.querySelector('ui-input');
inputEl.checked = true;
inputEl.dispatchEvent(new CustomEvent('change'));
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
const messageTexts = Array.from(
element.shadowRoot.querySelectorAll('p[class="error-message"]')
).map(errorMessage => (errorMessage = errorMessage.textContent));
expect(messageTexts).toEqual(ERROR_MESSAGES_OUTPUT);
});
});
});

Просмотреть файл

@ -0,0 +1,7 @@
:host > div {
text-align: center;
}
.padding-around-small {
padding: 4px;
}

Просмотреть файл

@ -0,0 +1,32 @@
<template>
<div class="padding-around-medium">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
id="error"
width="30px%"
height="30px"
>
<path
d="M12 .9C5.9.9.9 5.9.9 12s5 11.1 11.1 11.1 11.1-5 11.1-11.1S18.1.9 12 .9zM3.7 12c0-4.6 3.7-8.3 8.3-8.3 1.8 0 3.5.5 4.8 1.5L5.2 16.8c-1-1.3-1.5-3-1.5-4.8zm8.3 8.3c-1.8 0-3.5-.5-4.8-1.5L18.8 7.2c1 1.3 1.5 3 1.5 4.8 0 4.6-3.7 8.3-8.3 8.3z"
></path>
</svg>
<div class="padding-around-small">
<p>{friendlyMessage}</p>
<template if:true={errorMessages.length}>
<div>
<ui-input
label="Show Details"
type="checkbox"
onchange={handleCheckboxChange}
></ui-input>
</div>
<template if:true={viewDetails}>
<template for:each={errorMessages} for:item="message">
<p key={message} class="error-message">{message}</p>
</template>
</template>
</template>
</div>
</div>
</template>

Просмотреть файл

@ -0,0 +1,20 @@
import { LightningElement, api, track } from 'lwc';
import { reduceErrors } from 'recipe/ldsUtils';
export default class ErrorPanel extends LightningElement {
/** Generic / user-friendly message */
@api friendlyMessage = 'Error retrieving data';
@track viewDetails = false;
/** Single or array of errors */
@api errors;
get errorMessages() {
return reduceErrors(this.errors);
}
handleCheckboxChange(event) {
this.viewDetails = event.target.checked;
}
}

Просмотреть файл

@ -0,0 +1,52 @@
img {
width: 80px;
height: 80px;
border-radius: 50%;
margin-bottom: 8px;
}
recipe-contact-list-item-bubbling {
position: relative;
border: solid 1px #ecebea;
border-radius: 4px;
display: block;
margin: 8px 0;
padding: 14px 2px 0 2px;
}
recipe-contact-list-item-bubbling:before {
content: 'recipe-contact-list-item-bubbling';
color: #dddbda;
position: absolute;
top: -16px;
left: 4px;
background-color: #ffffff;
padding: 0 4px;
}
div.contacts {
display: flex;
}
.contact-details {
padding-left: 10px;
display: block;
max-width: 50%;
}
p {
margin: 0;
}
@media (max-width: 400px) {
.contact-details {
padding-left: 10px;
}
}
@media (max-width: 900px) {
.contact-list,
.contact-details {
max-width: 100%;
}
}

Просмотреть файл

@ -0,0 +1,47 @@
<template>
<ui-card title="EventBubbling">
<template if:true={contacts.data}>
<div class="contacts">
<!-- recipe-contact-list-item-bubbling emits a bubbling event so a single listener on a containing element works -->
<div class="contact-list" oncontactselect={handleContactSelect}>
<template for:each={contacts.data} for:item="contact">
<recipe-contact-list-item-bubbling
key={contact.Id}
contact={contact}
></recipe-contact-list-item-bubbling>
</template>
</div>
<div class="contact-details">
<template if:true={selectedContact}>
<img
src={selectedContact.Picture}
alt="Profile photo"
/>
<p>{selectedContact.Name}</p>
<p>{selectedContact.Title}</p>
<p>
<ui-output
type="phone"
value={selectedContact.Phone}
></ui-output>
</p>
<p>
<ui-output
type="email"
value={selectedContact.Email}
></ui-output>
</p>
</template>
</div>
</div>
</template>
<template if:true={contacts.error}>
<recipe-error-panel errors={contacts.error}></recipe-error-panel>
</template>
<recipe-view-source source="recipe/eventBubbling" slot="footer">
Child-to-grandparents communication using an event that bubbles and
is handled on a higher level element in the DOM tree. Click an item
in the list to see the recipe in action.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,12 @@
import { LightningElement, wire, track } from 'lwc';
import getContactList from 'data/wireGetContactListProvider';
export default class EventBubbling extends LightningElement {
@track selectedContact;
@wire(getContactList) contacts;
handleContactSelect(event) {
this.selectedContact = event.target.contact;
}
}

Просмотреть файл

@ -0,0 +1,64 @@
import { createElement } from 'lwc';
import EventSimple from 'recipe/eventSimple';
describe('recipe-event-simple', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('increments and decrements the page value by 1 on button click', () => {
// Create initial element
const element = createElement('recipe-event-simple', {
is: EventSimple
});
document.body.appendChild(element);
const paginatorEl = element.shadowRoot.querySelector(
'recipe-paginator'
);
const buttonEls = paginatorEl.shadowRoot.querySelectorAll('ui-button');
// First click "Next", so that the page property increments to 2
buttonEls.forEach(buttonEl => {
if (buttonEl.label === 'Next') {
buttonEl.click();
}
});
const pageEl = element.shadowRoot.querySelector('p');
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve()
.then(() => {
// Verify that property is correctly incremented.
expect(pageEl.textContent).toBe('Page 2');
// Now click "Previous", so that the page property decrements to 1
buttonEls.forEach(buttonEl => {
if (buttonEl.label === 'Previous') {
buttonEl.click();
}
});
})
.then(() => {
// Verify that property is correctly incremented.
expect(pageEl.textContent).toBe('Page 1');
// Decrement again
buttonEls.forEach(buttonEl => {
if (buttonEl.label === 'Previous') {
buttonEl.click();
}
});
})
.then(() => {
// Verify that property is not decremented, and the initial value stays on 1.
expect(pageEl.textContent).toBe('Page 1');
});
});
});

Просмотреть файл

@ -0,0 +1,21 @@
recipe-paginator {
position: relative;
border: solid 1px #ecebea;
border-radius: 4px;
display: block;
padding: 14px 2px 2px 2px;
}
recipe-paginator:before {
content: 'recipe-paginator';
color: #dddbda;
position: absolute;
top: -16px;
left: 4px;
background-color: #ffffff;
padding: 0 4px;
}
.center {
text-align: center;
}

Просмотреть файл

@ -0,0 +1,15 @@
<template>
<ui-card title="EventSimple">
<div>
<p class="center">Page {page}</p>
<recipe-paginator
onprevious={handlePrevious}
onnext={handleNext}
></recipe-paginator>
</div>
<recipe-view-source source="recipe/eventSimple" slot="footer">
Child-to-parent communication using a custom event.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,15 @@
import { LightningElement, track } from 'lwc';
export default class EventSimple extends LightningElement {
@track page = 1;
handlePrevious() {
if (this.page > 1) {
this.page = this.page - 1;
}
}
handleNext() {
this.page = this.page + 1;
}
}

Просмотреть файл

@ -0,0 +1,52 @@
img {
width: 80px;
height: 80px;
border-radius: 50%;
margin-bottom: 8px;
}
recipe-contact-list-item {
position: relative;
border: solid 1px #ecebea;
border-radius: 4px;
display: block;
margin: 8px 0;
padding: 14px 2px 0 2px;
}
recipe-contact-list-item:before {
content: 'recipe-contact-list-item';
color: #dddbda;
position: absolute;
top: -16px;
left: 4px;
background-color: #ffffff;
padding: 0 4px;
}
div.contacts {
display: flex;
}
.contact-details {
padding-left: 10px;
display: block;
max-width: 50%;
}
p {
margin: 0;
}
@media (max-width: 400px) {
.contact-details {
padding-left: 10px;
}
}
@media (max-width: 900px) {
.contact-list,
.contact-details {
max-width: 100%;
}
}

Просмотреть файл

@ -0,0 +1,48 @@
<template>
<ui-card title="EventWithData">
<template if:true={contacts.data}>
<div class="contacts">
<!-- recipe-contact-list-item emits a non-bubbling event so each element must have a listener-->
<div class="contact-list">
<template for:each={contacts.data} for:item="contact">
<recipe-contact-list-item
key={contact.Id}
contact={contact}
onselect={handleSelect}
></recipe-contact-list-item>
</template>
</div>
<div class="contact-details">
<template if:true={selectedContact}>
<img
src={selectedContact.Picture}
alt="Profile photo"
/>
<p>{selectedContact.Name}</p>
<p>{selectedContact.Title}</p>
<p>
<ui-output
type="phone"
value={selectedContact.Phone}
></ui-output>
</p>
<p>
<ui-output
type="email"
value={selectedContact.Email}
></ui-output>
</p>
</template>
</div>
</div>
</template>
<template if:true={contacts.error}>
<recipe-error-panel errors={contacts.error}></recipe-error-panel>
</template>
<recipe-view-source source="recipe/eventWithData" slot="footer">
Child-to-parent communication using a custom event that passes data
to the parent component. Click an item in the list to see the recipe
in action.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,15 @@
import { LightningElement, wire, track } from 'lwc';
import getContactList from 'data/wireGetContactListProvider';
export default class EventWithData extends LightningElement {
@track selectedContact;
@wire(getContactList) contacts;
handleSelect(event) {
const contactId = event.detail;
this.selectedContact = this.contacts.data.find(
contact => contact.Id === contactId
);
}
}

Просмотреть файл

@ -0,0 +1,23 @@
import { createElement } from 'lwc';
import Hello from 'recipe/hello';
describe('recipe-hello', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('displays greeting', () => {
// Create element
const element = createElement('recipe-hello', {
is: Hello
});
document.body.appendChild(element);
// Verify displayed greeting
const div = element.shadowRoot.querySelector('div');
expect(div.textContent).toBe('Hello, World!');
});
});

Просмотреть файл

@ -0,0 +1,11 @@
<template>
<ui-card title="Hello">
<div>
Hello, {greeting}!
</div>
<recipe-view-source source="recipe/hello" slot="footer">
Bind an HTML element to a component property.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,5 @@
import { LightningElement } from 'lwc';
export default class Hello extends LightningElement {
greeting = 'World';
}

Просмотреть файл

@ -0,0 +1,38 @@
import { createElement } from 'lwc';
import HelloBinding from 'recipe/helloBinding';
describe('recipe-hello-binding', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('displays greeting specified by change event target', () => {
const EXPECTED = 'Test';
// Create element
const element = createElement('recipe-hello-binding', {
is: HelloBinding
});
document.body.appendChild(element);
// Verify default greeting
let div = element.shadowRoot.querySelector('div');
expect(div.textContent).not.toBe(`Hello, ${EXPECTED}!`);
// Trigger new greeting
const inputEl = element.shadowRoot.querySelector('ui-input');
inputEl.value = EXPECTED;
inputEl.dispatchEvent(new CustomEvent('change'));
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
// Verify displayed greeting
expect(div.textContent).toBe(`Hello, ${EXPECTED}!`);
});
});
});

Просмотреть файл

@ -0,0 +1,18 @@
<template>
<ui-card title="HelloBinding">
<div>
<p>Hello, {greeting}!</p>
<ui-input
label="Name"
value={greeting}
onchange={handleChange}
></ui-input>
</div>
<recipe-view-source source="recipe/helloBinding" slot="footer">
Change the value of a bound property when the value of an input
field changes. Type something in the input field to see the recipe
in action.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,9 @@
import { LightningElement, track } from 'lwc';
export default class HelloBinding extends LightningElement {
@track greeting = 'World';
handleChange(event) {
this.greeting = event.target.value;
}
}

Просмотреть файл

@ -0,0 +1,45 @@
import { createElement } from 'lwc';
import HelloConditionalRendering from 'recipe/helloConditionalRendering';
describe('recipe-hello-conditional-rendering', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('does not show details by default', () => {
// Create element
const element = createElement('recipe-hello-conditional-rendering', {
is: HelloConditionalRendering
});
document.body.appendChild(element);
// Verify displayed message
const detailEl = element.shadowRoot.querySelector('.details');
expect(detailEl.textContent).toBe('Not showing details.');
});
it('shows details when checkbox toggled', () => {
// Create element
const element = createElement('recipe-hello-conditional-rendering', {
is: HelloConditionalRendering
});
document.body.appendChild(element);
// Toggle checkbox to show details
const inputEl = element.shadowRoot.querySelector('ui-input');
inputEl.checked = true;
inputEl.dispatchEvent(new CustomEvent('change'));
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
// Verify displayed message
const detailEl = element.shadowRoot.querySelector('.details');
expect(detailEl.textContent).toBe('These are the details!');
});
});
});

Просмотреть файл

@ -0,0 +1,27 @@
<template>
<ui-card title="HelloConditionalRendering">
<div>
<ui-input
type="checkbox"
label="Show details"
onchange={handleChange}
></ui-input>
<div class="details">
<template if:true={areDetailsVisible}>
These are the details!
</template>
<template if:false={areDetailsVisible}>
Not showing details.
</template>
</div>
</div>
<recipe-view-source
source="recipe/helloConditionalRendering"
slot="footer"
>
Conditionally render elements.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,9 @@
import { LightningElement, track } from 'lwc';
export default class HelloConditionalRendering extends LightningElement {
@track areDetailsVisible = false;
handleChange(event) {
this.areDetailsVisible = event.target.checked;
}
}

Просмотреть файл

@ -0,0 +1,84 @@
import { createElement } from 'lwc';
import HelloExpressions from 'recipe/helloExpressions';
const PREFIX = 'Uppercased Full Name:';
describe('recipe-hello-expressions', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
function setInputElementValues(element, firstName, lastName) {
// ui-input doesn't mirror its properties as attributes so
// can't use an attribute query selector.
element.shadowRoot.querySelectorAll('ui-input').forEach(input => {
if (firstName && input.name === 'firstName') {
input.value = firstName;
input.dispatchEvent(new CustomEvent('change'));
} else if (lastName && input.name === 'lastName') {
input.value = lastName;
input.dispatchEvent(new CustomEvent('change'));
}
});
}
it('displays first name as uppercase', () => {
// Create initial element
const element = createElement('recipe-hello-expressions', {
is: HelloExpressions
});
document.body.appendChild(element);
setInputElementValues(element, 'Peter', undefined);
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
// Verify displayed message
const detailEl = element.shadowRoot.querySelector('p');
expect(detailEl.textContent).toBe(`${PREFIX} PETER`);
});
});
it('displays last name as uppercase', () => {
// Create initial element
const element = createElement('recipe-hello-expressions', {
is: HelloExpressions
});
document.body.appendChild(element);
setInputElementValues(element, undefined, 'Pan');
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
// Verify displayed message
const detailEl = element.shadowRoot.querySelector('p');
expect(detailEl.textContent).toBe(`${PREFIX} PAN`);
});
});
it('displays first and last name as uppercase', () => {
// Create initial element
const element = createElement('recipe-hello-expressions', {
is: HelloExpressions
});
document.body.appendChild(element);
setInputElementValues(element, 'Peter', 'Pan');
// Return a promise to wait for any asynchronous DOM updates. Jest
// will automatically wait for the Promise chain to complete before
// ending the test and fail the test if the promise rejects.
return Promise.resolve().then(() => {
// Verify displayed message
const detailEl = element.shadowRoot.querySelector('p');
expect(detailEl.textContent).toBe(`${PREFIX} PETER PAN`);
});
});
});

Просмотреть файл

@ -0,0 +1,3 @@
.margin-top-medium {
margin: 8 0 0 0;
}

Просмотреть файл

@ -0,0 +1,24 @@
<template>
<ui-card title="HelloExpressions">
<div>
<ui-input
name="firstName"
label="First Name"
onchange={handleChange}
></ui-input>
<ui-input
name="lastName"
label="Last Name"
onchange={handleChange}
></ui-input>
<p class="margin-top-medium">
Uppercased Full Name: {uppercasedFullName}
</p>
</div>
<recipe-view-source source="recipe/helloExpressions" slot="footer">
Use JavaScript expressions in a template. Type something in the
input fields to see the recipe in action.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,19 @@
import { LightningElement, track } from 'lwc';
export default class HelloExpressions extends LightningElement {
@track firstName = '';
@track lastName = '';
handleChange(event) {
const field = event.target.name;
if (field === 'firstName') {
this.firstName = event.target.value;
} else if (field === 'lastName') {
this.lastName = event.target.value;
}
}
get uppercasedFullName() {
return `${this.firstName} ${this.lastName}`.trim().toUpperCase();
}
}

Просмотреть файл

@ -0,0 +1,31 @@
import { createElement } from 'lwc';
import HelloForEach from 'recipe/helloForEach';
describe('recipe-hello-for-each', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('displays contacts in specific order', () => {
const EXPECTED = [
'Amy Taylor, VP of Engineering',
'Michael Jones, VP of Sales',
'Jennifer Wu, CEO'
];
// Create initial element
const element = createElement('recipe-hello-for-each', {
is: HelloForEach
});
document.body.appendChild(element);
// Verify displayed list
const contacts = Array.from(
element.shadowRoot.querySelectorAll('li')
).map(li => li.textContent);
expect(contacts).toEqual(EXPECTED);
});
});

Просмотреть файл

@ -0,0 +1,4 @@
ul {
list-style: none;
padding: 0;
}

Просмотреть файл

@ -0,0 +1,14 @@
<template>
<ui-card title="HelloForEach">
<ul>
<template for:each={contacts} for:item="contact">
<li key={contact.Id}>
{contact.Name}, {contact.Title}
</li>
</template>
</ul>
<recipe-view-source source="recipe/helloForEach" slot="footer">
Loop through an array of items in a template.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,21 @@
import { LightningElement } from 'lwc';
export default class HelloForEach extends LightningElement {
contacts = [
{
Id: '003171931112854375',
Name: 'Amy Taylor',
Title: 'VP of Engineering'
},
{
Id: '003192301009134555',
Name: 'Michael Jones',
Title: 'VP of Sales'
},
{
Id: '003848991274589432',
Name: 'Jennifer Wu',
Title: 'CEO'
}
];
}

Просмотреть файл

@ -0,0 +1,49 @@
import { createElement } from 'lwc';
import HelloIterator from 'recipe/helloIterator';
describe('recipe-hello-iterator', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('displays contacts in specific order', () => {
const EXPECTED = [
'Amy Taylor, VP of Engineering',
'Michael Jones, VP of Sales',
'Jennifer Wu, CEO'
];
// Create initial element
const element = createElement('recipe-hello-iterator', {
is: HelloIterator
});
document.body.appendChild(element);
// Verify displayed list
const contacts = Array.from(
element.shadowRoot.querySelectorAll('li')
).map(li => li.textContent);
expect(contacts).toEqual(EXPECTED);
});
it('displays div in first and last contacts', () => {
// Create initial element
const element = createElement('recipe-hello-iterator', {
is: HelloIterator
});
document.body.appendChild(element);
// Verify first ul's first child is a div
expect(
element.shadowRoot.querySelector('ul:first-child').firstChild
.tagName
).toBe('DIV');
// Verify last li's last child is a div
expect(
element.shadowRoot.querySelector('li:last-child').lastChild.tagName
).toBe('DIV');
});
});

Просмотреть файл

@ -0,0 +1,14 @@
.list-first {
border-top: 1px solid #706e6b;
padding-top: 5px;
}
.list-last {
border-bottom: 1px solid #706e6b;
padding-bottom: 5px;
}
ul {
list-style: none;
padding: 0;
}

Просмотреть файл

@ -0,0 +1,22 @@
<template>
<ui-card title="HelloIterator">
<ul>
<template iterator:it={contacts}>
<div
if:true={it.first}
key={it.value.Id}
class="list-first"
></div>
<li key={it.value.Id}>
{it.value.Name}, {it.value.Title}
<div if:true={it.last} class="list-last"></div>
</li>
</template>
</ul>
<recipe-view-source source="recipe/helloIterator" slot="footer">
Loop through an array with special behavior for the first and last
items.
</recipe-view-source>
</ui-card>
</template>

Просмотреть файл

@ -0,0 +1,21 @@
import { LightningElement } from 'lwc';
export default class HelloIterator extends LightningElement {
contacts = [
{
Id: '003171931112854375',
Name: 'Amy Taylor',
Title: 'VP of Engineering'
},
{
Id: '003192301009134555',
Name: 'Michael Jones',
Title: 'VP of Sales'
},
{
Id: '003848991274589432',
Name: 'Jennifer Wu',
Title: 'CEO'
}
];
}

Просмотреть файл

@ -0,0 +1,37 @@
/**
* Reduces one or more LDS errors into a string[] of error messages.
* @param {FetchResponse|FetchResponse[]} errors
* @return {String[]} Error messages
*/
export function reduceErrors(errors) {
if (!Array.isArray(errors)) {
errors = [errors];
}
return (
errors
// Remove null/undefined items
.filter(error => !!error)
// Extract an error message
.map(error => {
// UI API read errors
if (Array.isArray(error.body)) {
return error.body.map(e => e.message);
}
// UI API DML, Apex and network errors
else if (error.body && typeof error.body.message === 'string') {
return error.body.message;
}
// JS errors
else if (typeof error.message === 'string') {
return error.message;
}
// Unknown error shape so try HTTP status text
return error.statusText;
})
// Flatten
.reduce((prev, curr) => prev.concat(curr), [])
// Remove empty strings
.filter(message => !!message)
);
}

Просмотреть файл

@ -0,0 +1,25 @@
import { createElement } from 'lwc';
import LibsChartjs from 'recipe/libsChartjs';
describe('recipe-libs-chartjs', () => {
afterEach(() => {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
// Clear mocks so that every test run has a clean implementation
jest.clearAllMocks();
});
it('contains a canvas element for ChartJs', () => {
// Create initial element
const element = createElement('recipe-libs-chartjs', {
is: LibsChartjs
});
document.body.appendChild(element);
// Querying the DOM element that has the lwc:dom directive set.
const domEl = element.shadowRoot.querySelector('canvas[class="donut"]');
expect(domEl).not.toBeNull();
});
});

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше