@@ -11,7 +11,7 @@ context('Web Form', () => { | |||||
cy.get('.modal.show > .modal-dialog').should('be.visible'); | cy.get('.modal.show > .modal-dialog').should('be.visible'); | ||||
}); | }); | ||||
it('Timeline should have submit and cancel activity information', () => { | |||||
it('Navigate and Submit a MultiStep WebForm', () => { | |||||
cy.call('frappe.tests.ui_test_helpers.update_webform_to_multistep').then(() => { | cy.call('frappe.tests.ui_test_helpers.update_webform_to_multistep').then(() => { | ||||
cy.visit('/update-profile'); | cy.visit('/update-profile'); | ||||
cy.get_field('last_name', 'Data').type('_Test User', {force: true}).wait(200); | cy.get_field('last_name', 'Data').type('_Test User', {force: true}).wait(200); | ||||
@@ -46,14 +46,6 @@ frappe.pages['setup-wizard'].on_page_load = function (wrapper) { | |||||
slide_class: frappe.setup.SetupWizardSlide, | slide_class: frappe.setup.SetupWizardSlide, | ||||
unidirectional: 1, | unidirectional: 1, | ||||
done_state: 1, | done_state: 1, | ||||
before_load: ($footer) => { | |||||
$footer.find('.next-btn').removeClass('btn-default') | |||||
.addClass('btn-primary'); | |||||
$footer.find('.text-right').prepend( | |||||
$(`<button class="complete-btn btn btn-sm primary"> | |||||
${__("Complete Setup")}</button>`)); | |||||
} | |||||
} | } | ||||
frappe.wizard = new frappe.setup.SetupWizard(wizard_settings); | frappe.wizard = new frappe.setup.SetupWizard(wizard_settings); | ||||
frappe.setup.run_event("after_load"); | frappe.setup.run_event("after_load"); | ||||
@@ -97,7 +89,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { | |||||
super.make(); | super.make(); | ||||
this.container.addClass("container setup-wizard-slide with-form"); | this.container.addClass("container setup-wizard-slide with-form"); | ||||
this.$next_btn.addClass('action'); | this.$next_btn.addClass('action'); | ||||
this.$complete_btn = this.$footer.find('.complete-btn').addClass('action'); | |||||
this.$complete_btn.addClass('action'); | |||||
this.setup_keyboard_nav(); | this.setup_keyboard_nav(); | ||||
} | } | ||||
@@ -145,7 +137,6 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { | |||||
this.$next_btn.removeClass("btn-primary").hide(); | this.$next_btn.removeClass("btn-primary").hide(); | ||||
this.$complete_btn.addClass("btn-primary").show() | this.$complete_btn.addClass("btn-primary").show() | ||||
.on('click', () => this.action_on_complete()); | .on('click', () => this.action_on_complete()); | ||||
} else { | } else { | ||||
this.$next_btn.addClass("btn-primary").show(); | this.$next_btn.addClass("btn-primary").show(); | ||||
this.$complete_btn.removeClass("btn-primary").hide(); | this.$complete_btn.removeClass("btn-primary").hide(); | ||||
@@ -178,6 +169,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { | |||||
this.setup(); | this.setup(); | ||||
this.show_slide(this.current_id); | this.show_slide(this.current_id); | ||||
this.refresh(this.current_id); | |||||
setTimeout(() => { | setTimeout(() => { | ||||
this.container.find('.form-control').first().focus(); | this.container.find('.form-control').first().focus(); | ||||
}, 200); | }, 200); | ||||
@@ -347,7 +339,6 @@ frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide { | |||||
// Frappe slides settings | // Frappe slides settings | ||||
// ====================================================== | // ====================================================== | ||||
frappe.setup.slides_settings = [ | frappe.setup.slides_settings = [ | ||||
{ | { | ||||
// Welcome (language) slide | // Welcome (language) slide | ||||
@@ -365,10 +356,10 @@ frappe.setup.slides_settings = [ | |||||
onload: function (slide) { | onload: function (slide) { | ||||
this.setup_fields(slide); | this.setup_fields(slide); | ||||
let browser_language = frappe.setup.utils.get_language_name_from_code(navigator.language); | |||||
let language_field = slide.get_field("language"); | |||||
var language_field = slide.get_field("language"); | |||||
language_field.set_input(frappe.setup.data.default_language || "English"); | |||||
language_field.set_input(browser_language || "English"); | |||||
if (!frappe.setup._from_load_messages) { | if (!frappe.setup._from_load_messages) { | ||||
language_field.$input.trigger("change"); | language_field.$input.trigger("change"); | ||||
@@ -532,12 +523,18 @@ frappe.setup.utils = { | |||||
/* | /* | ||||
Set a slide's country, timezone and currency fields | Set a slide's country, timezone and currency fields | ||||
*/ | */ | ||||
var data = frappe.setup.data.regional_data; | |||||
var country_field = slide.get_field('country'); | |||||
let data = frappe.setup.data.regional_data; | |||||
let country_field = slide.get_field('country'); | |||||
let translated_countries = []; | |||||
Object.keys(data.country_info).sort().forEach(country => { | |||||
translated_countries.push({ | |||||
label: __(country), | |||||
value: country | |||||
}); | |||||
}); | |||||
country_field.set_data(Object.keys(data.country_info).sort()); | |||||
country_field.set_data(translated_countries); | |||||
slide.get_input("currency") | slide.get_input("currency") | ||||
.empty() | .empty() | ||||
@@ -584,6 +581,10 @@ frappe.setup.utils = { | |||||
}); | }); | ||||
}, | }, | ||||
get_language_name_from_code: function (language_code) { | |||||
return frappe.setup.data.lang.codes_to_names[language_code] || "English"; | |||||
}, | |||||
bind_region_events: function (slide) { | bind_region_events: function (slide) { | ||||
/* | /* | ||||
Bind a slide's country, timezone and currency fields | Bind a slide's country, timezone and currency fields | ||||
@@ -190,7 +190,7 @@ class Document(BaseDocument): | |||||
:param permtype: one of `read`, `write`, `submit`, `cancel`, `delete`""" | :param permtype: one of `read`, `write`, `submit`, `cancel`, `delete`""" | ||||
if self.flags.ignore_permissions: | if self.flags.ignore_permissions: | ||||
return True | return True | ||||
return frappe.has_permission(self.doctype, permtype, self, verbose=verbose) | |||||
return frappe.permissions.has_permission(self.doctype, permtype, self, verbose=verbose) | |||||
def raise_no_permission_to(self, perm_type): | def raise_no_permission_to(self, perm_type): | ||||
"""Raise `frappe.PermissionError`.""" | """Raise `frappe.PermissionError`.""" | ||||
@@ -456,6 +456,8 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat | |||||
this.docname, value); | this.docname, value); | ||||
} | } | ||||
validate_link_and_fetch(df, options, docname, value) { | validate_link_and_fetch(df, options, docname, value) { | ||||
if (!options) return; | |||||
let field_value = ""; | let field_value = ""; | ||||
const fetch_map = this.fetch_map; | const fetch_map = this.fetch_map; | ||||
const columns_to_fetch = Object.values(fetch_map); | const columns_to_fetch = Object.values(fetch_map); | ||||
@@ -21,7 +21,7 @@ frappe.ui.Slide = class Slide { | |||||
this.$body = $(`<div class="slide-body"> | this.$body = $(`<div class="slide-body"> | ||||
<div class="content text-center"> | <div class="content text-center"> | ||||
<h1 class="title slide-title">${this.title}</h1> | |||||
<h1 class="title slide-title">${__(this.title)}</h1> | |||||
</div> | </div> | ||||
<div class="form-wrapper"> | <div class="form-wrapper"> | ||||
<div class="form"></div> | <div class="form"></div> | ||||
@@ -40,7 +40,7 @@ frappe.ui.Slide = class Slide { | |||||
if (this.image_src) this.$content.append( | if (this.image_src) this.$content.append( | ||||
$(`<img src="${this.image_src}" style="margin: 20px;">`)); | $(`<img src="${this.image_src}" style="margin: 20px;">`)); | ||||
if (this.help) this.$content.append($(`<p class="slide-help">${this.help}</p>`)); | |||||
if (this.help) this.$content.append($(`<p class="slide-help">${__(this.help)}</p>`)); | |||||
this.reqd_fields = []; | this.reqd_fields = []; | ||||
@@ -263,7 +263,7 @@ frappe.ui.Slides = class Slides { | |||||
.appendTo(this.container); | .appendTo(this.container); | ||||
this.render_progress_dots(); | this.render_progress_dots(); | ||||
this.make_prev_next_buttons(); | |||||
this.make_prev_next_complete_buttons(); | |||||
if (this.before_load) this.before_load(this.$footer); | if (this.before_load) this.before_load(this.$footer); | ||||
// can be on demand | // can be on demand | ||||
@@ -289,6 +289,7 @@ frappe.ui.Slides = class Slides { | |||||
} | } | ||||
} else { | } else { | ||||
if (this.made_slide_ids.includes(id + "")) { | if (this.made_slide_ids.includes(id + "")) { | ||||
this.slide_dict[id].done = false; | |||||
this.slide_dict[id].destroy(); | this.slide_dict[id].destroy(); | ||||
this.slide_dict[id].make(); | this.slide_dict[id].make(); | ||||
} | } | ||||
@@ -298,6 +299,7 @@ frappe.ui.Slides = class Slides { | |||||
refresh(id) { | refresh(id) { | ||||
this.render_progress_dots(); | this.render_progress_dots(); | ||||
this.make_prev_next_complete_buttons(); | |||||
this.show_hide_prev_next(id); | this.show_hide_prev_next(id); | ||||
this.$body.find('.form-control').first().focus(); | this.$body.find('.form-control').first().focus(); | ||||
} | } | ||||
@@ -338,13 +340,16 @@ frappe.ui.Slides = class Slides { | |||||
if (!this.unidirectional) this.bind_progress_dots(); | if (!this.unidirectional) this.bind_progress_dots(); | ||||
} | } | ||||
make_prev_next_buttons() { | |||||
make_prev_next_complete_buttons() { | |||||
this.$footer.empty(); | |||||
$(`<div class="row"> | $(`<div class="row"> | ||||
<div class="col-sm-4 text-left prev-div"> | <div class="col-sm-4 text-left prev-div"> | ||||
<button class="prev-btn btn btn-secondary btn-sm" tabindex="0">${__("Previous")}</button> | |||||
<button class="prev-btn btn btn-secondary btn-sm" tabindex="0">${__("Previous", null, "Go to previous slide")}</button> | |||||
</div> | </div> | ||||
<div class="col-sm-8 text-right next-div"> | <div class="col-sm-8 text-right next-div"> | ||||
<button class="next-btn btn btn-default btn-sm" tabindex="0">${__("Next")}</button> | |||||
<button class="complete-btn btn btn-sm primary">${__("Complete Setup", null, "Finish the setup wizard")}</button> | |||||
<button class="next-btn btn btn-default btn-sm" tabindex="0">${__("Next", null, "Go to next slide")}</button> | |||||
</div> | </div> | ||||
</div>`).appendTo(this.$footer); | </div>`).appendTo(this.$footer); | ||||
@@ -361,6 +366,8 @@ frappe.ui.Slides = class Slides { | |||||
this.show_slide(this.current_id + 1); | this.show_slide(this.current_id + 1); | ||||
} | } | ||||
}); | }); | ||||
this.$complete_btn = this.$footer.find('.complete-btn').attr('tabIndex', 0); | |||||
} | } | ||||
bind_progress_dots() { | bind_progress_dots() { | ||||
@@ -90,8 +90,8 @@ export default class WebForm extends frappe.ui.FieldGroup { | |||||
if (!is_validated) return; | if (!is_validated) return; | ||||
/* | |||||
eslint cannot figure out if this is an infinite loop in backwards and | |||||
/** | |||||
The eslint utility cannot figure out if this is an infinite loop in backwards and | |||||
throws an error. Disabling for-direction just for this section. | throws an error. Disabling for-direction just for this section. | ||||
for-direction doesnt throw an error if the values are hardcoded in the | for-direction doesnt throw an error if the values are hardcoded in the | ||||
reverse for-loop, but in this case its a dynamic loop. | reverse for-loop, but in this case its a dynamic loop. | ||||
@@ -474,6 +474,24 @@ class TestCommands(BaseTestCommands): | |||||
# cleanup | # cleanup | ||||
shutil.rmtree(test_app_path) | shutil.rmtree(test_app_path) | ||||
def disable_test_bench_drop_site_should_archive_site(self): | |||||
site = 'test_site.localhost' | |||||
self.execute( | |||||
f"bench new-site {site} --force --verbose --admin-password {frappe.conf.admin_password} " | |||||
f"--mariadb-root-password {frappe.conf.root_password}" | |||||
) | |||||
self.assertEqual(self.returncode, 0) | |||||
self.execute(f"bench drop-site {site} --force --root-password {frappe.conf.root_password}") | |||||
self.assertEqual(self.returncode, 0) | |||||
bench_path = frappe.utils.get_bench_path() | |||||
site_directory = os.path.join(bench_path, f'sites/{site}') | |||||
self.assertFalse(os.path.exists(site_directory)) | |||||
archive_directory = os.path.join(bench_path, f'archived/sites/{site}') | |||||
self.assertTrue(os.path.exists(archive_directory)) | |||||
class RemoveAppUnitTests(unittest.TestCase): | class RemoveAppUnitTests(unittest.TestCase): | ||||
def test_delete_modules(self): | def test_delete_modules(self): | ||||