I am very new to lit-element and unit testing and am not sure if the question is even framed correctly. Please correct me if I am wrong anywhere.
So, I have 1 lit component created such that it fetches the json from the uri
<abc infoJsonLink="../data/data.json"></abc>
data.json
[
{"id":1, "name":"one", "img":"one.jpg"},
{"id":1, "name":"two", "img":"two.jpg"},
...]
Within abc's render() method, we loop through the json data and create a array with html markup corresponding to each item, itemsMarkupList
*0* <div "item-data"><span>one</span><img src="one.jpg"/></div>
*1* <div "item-data"><span>two</span><img src="two.jpg"/></div>
*2* ...
Each one of these markups, has to be rendered in a bootstrap carousel. https://mdbootstrap.com/snippets/jquery/ascensus/135508
For this we have created another component, <carousel-helper>, which has the code to accept an array of markups and render it as a carousel using bootstrap libraries.
Below is the render() method of <abc> component
render(){...
let ch = document.createElement("carousel-helper");
ch.itemsMarkupList = this.itemsMarkupList;
return html`... <div class="parent_wrapper"> ${ch} </div>`;
}
This whole renders perfectly when I npm run it.
But when I am writing the test cases for this, the shadowroot of child elements seems to be not rendering.
it("test1", async () => {
const el = /** #type {Abc} */ (await fixture(
html`<abc infoJsonLink="/data/data.json"></abc>
`));
await el.loadJsonData();
/*statement 1*/
console.log(el.shadowRoot.querySelector("carousel-helper"));
/*statement 2*/
console.log(el.shadowRoot.querySelector("carousel-helper").
shadowRoot.querySelectorAll(".item-data"));
});
statement-1 works and returns <carousel-helper></carousel-helper>
statement-2 return and empty array, NodeList {}
Can anyone tell me what I need to do to get a filled array from statement-2?
Related
I am writing Cypress tests for an application that has a dynamic group/list of items. Each item has a details link that when clicked creates a popup with said details. I need to close that popup and move to the next item in the group.
I first tried to use the .each() command on the group and then would .click({multiple:true}) the details but the popup would cover the the next click. Adding {foce:true} does allow all of the popups to display but I don't think that is in the spirit of how the application should function.
My latest attempt has been to create a custom command using .next() to iterate through the group. This works but when .next() reaches the end of the group it yields "" and so the test ultimately fails.
The actual error I get is:
Expected to find element: ``, but never found it. Queried from element: <div.groups.ng-star-inserted>
the .spec.ts
describe('Can select incentives and view details', () => {
it('Views incentive details', () => {
cy.optionPop('section#Incentives div.groups:first')
})
})
the index.ts
Cypress.Commands.add('optionPop', (clickable) => {
cy.get(clickable).find('[ng-reflect-track="Estimator, open_selection_dial"]').click()
cy.get('mat-dialog-container i.close').click()
cy.get(clickable).next().as('clicked').then(($clicked) => {
//fails at .next ^ because '' is yielded at end of list
cy.wrap($clicked).should('not.eq','')
})
cy.optionPop('#clicked')
})
You basically have the right idea, but it might work better in a plain JS function rather than a custom command.
function openPopups(clickables) {
if (clickables.length === 0) return // exit when array is empty
const clickable = clickables.pop() // extract one and reduce array
cy.wrap(clickable)
.find('[ng-reflect-track="Estimator, open_selection_dial"]').click()
cy.get('mat-dialog-container i.close')
.should('be.visible') // in case popup is slow
.click()
// wait for this popup to go, then proceed to next
cy.get('mat-dialog-container')
.should('not.be.visible')
.then(() => {
openPopups(clickables) // clickables now has one less item
})
}
cy.get('section#Incentives div.groups') // get all the popups
.then($popups => {
const popupsArray = Array.from($popups) // convert jQuery result to array
openPopups(popupsArray)
})
Some extra notes:
Using Array.from($popups) because we don't know how many in the list, and want to use array.pop() to grab each item and at the same time reduce the array (it's length will control the loop exit).
clickables is a list of raw elements, so cy.wrap(clickable) makes the individual element usable with Cypress commands like .find()
.should('be.visible') - when dealing with popup, the DOM is often altered by the click event that opens it, which can be slow relative to the speed the test runs at. Adding .should('be.visible') is a guard to make sure the test is not flaky on some runs (e.g if using CI)
.should('not.be.visible').then(() => ... - since you have some problems with multiple overlapping popups this will ensure each popup has gone before testing the next one.
I would like to assign a template on my PageLayoutView preview in order to avoid writing HTML on my PHP files and better maintain it. How can i achieve that using TYPO3 10?
I would like to use it for example on the textmedia content element and add the subheader in.
In order to achieve that, you will have to use the Standalone view. I would write a function which i can call it whenever i want to use it. Normally i would include the function on a Helper Class but for the sake of this answer i will put it on the same class. So lets say we have the textmedia content element and we want to add fields on the backend Preview.
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
/**
* Contains a preview rendering for the page module of CType="textmedia"
*/
class TextMediaPreviewRenderer implements PageLayoutViewDrawItemHookInterface
{
/**
* Preprocesses the preview rendering of a content element of type "textmedia"
*
* #param \TYPO3\CMS\Backend\View\PageLayoutView $parentObject Calling parent object
* #param bool $drawItem Whether to draw the item using the default functionality
* #param string $headerContent Header content
* #param string $subheaderContent Subheader content
* #param string $itemContent Item content
* #param array $row Record row of tt_content
*/
public function preProcess(
PageLayoutView &$parentObject,
&$drawItem,
&$headerContent,
&$subheaderContent,
&$itemContent,
array &$row
) {
if ($row['CType'] === 'textmedia') {
$standaloneView = $this->getStandAloneConfig();
/*Disable TYPO3's default backend view configuration */
$drawItem = false;
/*Assign all the results to the backend */
$standaloneView->assignMultiple([
'title' => $parentObject->CType_labels[$row['CType']],
'type' => $row['CType'],
'content' => $row,
]);
$itemContent .= $standaloneView->render();
}
}
public function getStandAloneConfig()
{
$standaloneView = GeneralUtility::makeInstance(StandaloneView::class);
$standaloneView->setLayoutRootPaths([10,'EXT:your_extension/Resources/Private/Backend/Layouts/']);
$standaloneView->setTemplateRootPaths([10,'EXT:your_extension/Resources/Private/Backend/Templates/']);
$standaloneView->setPartialRootPaths([10,'EXT:your_extension/Resources/Private/Backend/Partials/']);
$standaloneView->setFormat('html');
$standaloneView->setTemplate('PageLayoutView.html');
return $standaloneView;
}
What is happening is the following
First, we get the StandAlone configuration. In this configuration (getStandAloneConfig()) we set the path to where our templates, partials and layouts are. Then we assign the type (html) and then the name of the template where our preview is going to be built on (PageLayoutView.html).
After that we reset everything that TYPO3 writes on the Preview ($drawItem = false;) so we can write our own.
Last and not least we assign the variables which we are going to use on the HTML file.
Under your_extension/Resources/Private/Backend/Templates/PageLayoutView.html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:switch expression="{type}">
<f:case value="textmedia">
<f:render partial="Textmedia" arguments="{_all}"/>
</f:case>
<f:case value="text">
<f:render partial="Text" arguments="{_all}"/>
</f:case>
<f:defaultCase>
<f:render partial="Header" arguments="{_all}"/>
</f:defaultCase>
</f:switch>
</html>
I personally use the PageLayoutView.html as a controller which decides, which partial should be rendered. So i assign the variable {type} and i render the partial based on its value.
Under your_extension/Resources/Private/Backend/Partials/Textmedia.html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<p><strong class="element_title">{title}</strong></p>
<table class="element_table">
<tbody>
<tr>
<th>Subheader</th>
<td>{content.subheader}</td>
</tr>
</tbody>
</table>
</html>
Now you can look what is inside the {content} variable and render it on your HTML.
If you want to style your Preview you can do the following: Under your ext_tables.php add the following line:
$GLOBALS['TBE_STYLES']['stylesheet'] = 'EXT:your_extension/Resources/Public/Css/Backend/page-layout-view.css';
There's a pretty easy way. Just set an alternative path for the preview templates.
\TYPO3\CMS\Backend\View\PageLayoutView::tt_content_drawItem():
// If the previous hook did not render something,
// then check if a Fluid-based preview template was defined for this CType
// and render it via Fluid. Possible option:
// mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html
if ($drawItem) {
$fluidPreview = $this->renderContentElementPreviewFromFluidTemplate($row);
if ($fluidPreview !== null) {
$out .= $fluidPreview;
$drawItem = false;
}
}
I am very new to testing and I'm struggling my way through all this new stuff I am learning. Today I want to write a test for a vuetify <v-text-field> component like this:
<v-text-field
v-model="user.caption"
label="Name"
:disabled="!checkPermissionFor('users.write')"
required
/>
my test should handle the following case:
an active, logged in user has a array in vuex store which has his permissions as a array of strings. exactly like this
userRights: ['dashboard', 'imprint', 'dataPrivacy']
the checkPermissionFor() function is doing nothing else then checking the array above with a arr.includes('x')
after it came out the right is not included it gives me a negotiated return which handles the disabled state on that input field.
I want to test this exact scenario.
my test at the moment looks like this:
it('user has no rights to edit other user overview data', () => {
const store = new Vuex.Store({
state: {
ActiveUser: {
userData: {
isLoggedIn: true,
isAdmin: false,
userRights: ['dashboard', 'imprint', 'dataPrivacy']
}
}
}
})
const wrapper = shallowMount(Overview, {
store,
localVue
})
const addUserPermission = wrapper.vm.checkPermissionFor('users.write')
const inputName = wrapper.find(
'HOW TO SELECT A INPUT LIKE THIS? DO I HAVE TO ADD A CLASS FOR IT?'
)
expect(addUserPermission).toBe(false)
expect(inputName.props('disabled')).toBe(false)
})
big questions now:
how can I select a input from vuetify which has no class like in my case
how can I test for "is the input disabled?"
wrapper.find method accepts a query string. You can pass a query string like this :
input[label='Name'] or if you know the exact index you can use this CSS query too : input:nth-of-type(2).
Then find method will return you another wrapper. Wrapper has a property named element which returns the underlying native element.
So you can check if input disabled like this :
const buttonWrapper = wrapper.find("input[label='Name']");
const isDisabled = buttonWrapper.element.disabled === true;
expect(isDisabled ).toBe(true)
For question 1 it's a good idea to put extra datasets into your component template that are used just for testing so you can extract that element - the most common convention is data-testid="test-id".
The reason you should do this instead of relying on the classes and ids and positional selectors or anything like that is because those selectors are likely to change in a way that shouldn't break your test - if in the future you change css frameworks or change an id for some reason, your tests will break even though your component is still working.
If you're (understandably) worried about polluting your markup with all these data-testid attributes, you can use a webpack plugin like https://github.com/emensch/vue-remove-attributes to strip them out of your dev builds. Here's how I use that with laravel mix:
const createAttributeRemover = require('vue-remove-attributes');
if (mix.inProduction()) {
mix.options({
vue: {
compilerOptions: {
modules: [
createAttributeRemover('data-testid')
]
}
}
})
}
as for your second question I don't know I was googling the same thing and I landed here!
I have a fiddle http://jsfiddle.net/kristaps_petersons/9wteJ/2/ it loads 3 objects and shows them in a view. Data is shown alright, but i can not filter it before i show it.
This
nodes: function(){
this.get('controller.content').filter(function(item, idx, en){
console.log('should log this atleast 3x')
})
return this.get('controller.content')
}.property('controller.content')
method is called when template iterates over array of values, but it never goes in to the loop and print console.log('should log this atleast 3x') why is that?
You are trying to replace controller.content while also binding to it. You need to define another property, such as filteredContent and bind it to controller.content. Take a look at how Ember.SortableMixin computes the variable arrangedContent for controllers with a sortProperties variable defined. Using that method as a template I would implement it like this:
filteredContent: Ember.computed('content', function() {
var content = this.get('content');
return this.filter(function(item, idx, en) {
console.log('should log this atleast 3x');
});
}).cacheable()
This should be implemented in the controller, not the view. The controller is the place for data manipulation, computed properties, and bindings.
Then bind the view layout to filteredContent instead of content to show the filtered data. Then both the original content and the filtered content are available.
Ok i got it working, but it feels a bit strange. First i moved method to Controller class and changed it to look like this:
nodes: function(){
console.log('BEFORE should log this atleast 3x', this.get('content.length'))
this.get('content').forEach(function(item, idx, en){
console.log('should log this atleast 3x')
})
console.log('AFTER should log this atleast 3x', this.get('content.length'))
return this.get('content')
}.property('content').cacheable()
as it should be same as buuda's recomedation, because as i understand from docs .poperty() is the same as Ember.computed. As it was still not working, i changed .property('content') to .property('content.#each') and it was working. Fiddle: http://jsfiddle.net/kristaps_petersons/9wteJ/21/ . I guess, that tempate first creates a binding to controller.content and as content itself does not change does not notify this method again, instead template pulls data as it becomes available.
I need to put a search box within a list of objects as a result of a typical indexSuccess action in Symfony. The goal is simple: filter the list according to a criteria.
I've been reading the Zend Lucene approach in Jobeet tutorial, but it seems like using a sledge-hammer to crack a nut (at least for my requirements).
I'm more interested in the auto-generated admin filter forms but I don't know how to implement it in a frontend.
I could simply pass the search box content to the action and build a custom query, but is there any better way to do this?
EDIT
I forgot to mention that I would like to have a single generic input field instead of an input field for each model attribute.
Thanks!
I'm using this solution, instead of integrating Zend Lucene I manage to use the autogenerated Symonfy's filters. This is the way i'm doing it:
//module/actions.class.php
public function executeIndex(sfWebRequest $request)
{
//set the form filter
$this->searchForm = new EmployeeFormFilter();
//bind it empty to fetch all data
$this->searchForm->bind(array());
//fetch all
$this->employees = $this->searchForm->getQuery()->execute();
...
}
I made a search action which does the search
public function executeSearch(sfWebRequest $request)
{
//create filter
$this->searchForm = new EmployeeFormFilter();
//bind parameter
$fields = $request->getParameter($this->searchForm->getName());
//bind
$this->searchForm->bind($fields);
//set paginator
$this->employees = $this->searchForm->getQuery()->execute();
...
//template
$this->setTemplate("index");
}
It's important that the search form goes to mymodule/search action.
Actually, i'm also using the sfDoctrinePager for paginate setting directly the query that the form generate to get results properly paginated.
If you want to add more fields to the search form check this :)
I finally made a custom form using the default MyModuleForm generated by Symfony
public function executeIndex {
...
// Add a form to filter results
$this->form = new MyModuleForm();
}
but displaying only a custom field:
<div id="search_box">
<input type="text" name="criteria" id="search_box_criteria" value="Search..." />
<?php echo link_to('Search', '#my_module_search?criteria=') ?>
</div>
Then I created a route named #my_module_search linked to the index action:
my_module_search:
url: my_module/search/:criteria
param: { module: my_module, action: index }
requirements: { criteria: .* } # Terms are optional, show all by default
With Javascript (jQuery in this case) I append the text entered to the criteria parameter in the href attribute of the link:
$('#search_box a').click(function(){
$(this).attr('href', $(this).attr('href') + $(this).prev().val());
});
And finally, back to the executeIndex action, I detect if text was entered and add custom filters to the DoctrineQuery object:
public function executeIndex {
...
// Deal with search criteria
if ( $text = $request->getParameter('criteria') ) {
$query = $this->pager->getQuery()
->where("MyTable.name LIKE ?", "%$text%")
->orWhere("MyTable.remarks LIKE ?", "%$text%")
...;
}
$this->pager->setQuery($query);
...
// Add a form to filter results
$this->form = new MyModuleForm();
}
Actually, the code is more complex, because I wrote some partials and some methods in parent classes to reuse code. But this is the best I can came up with.