Покрыть тестами - заполнение курсов.

remotes/origin/feature/testing_courses_30-01-19
gzbender 7 years ago
parent 14ddc2b28c
commit 15954fc66a
  1. 85
      apps/course/tests/test_views.py
  2. 44
      project/tests/__init__.py
  3. 96
      project/utils/selenium_utils.py
  4. 2
      requirements.txt
  5. 71
      web/src/components/CourseRedactor.vue

@ -2,14 +2,19 @@ from datetime import timedelta
from random import randint from random import randint
from selenium import webdriver from selenium import webdriver
from factory.faker import Faker
from django.utils import timezone from django.utils import timezone
from django.test import TestCase from django.test import TestCase
from django.shortcuts import reverse from django.shortcuts import reverse
from django.conf import settings
from django.utils.text import slugify
from unidecode import unidecode
from project.tests.factories import * from project.tests.factories import *
from project.tests import SeleniumTestCase
from project.utils.selenium_utils import SeleniumExtensions as SE from project.utils.selenium_utils import SeleniumExtensions as SE
'''
class CoursesTestCase(TestCase): class CoursesTestCase(TestCase):
@classmethod @classmethod
@ -18,6 +23,11 @@ class CoursesTestCase(TestCase):
create_batch_unique(CourseFactory, status=Course.STATUS_CHOICES[:3], price=[0, 1000], create_batch_unique(CourseFactory, status=Course.STATUS_CHOICES[:3], price=[0, 1000],
age=Course.AGE_CHOICES[:2], deferred_start_at=[None, timezone.now() + timedelta(days=randint(5, 15))]) age=Course.AGE_CHOICES[:2], deferred_start_at=[None, timezone.now() + timedelta(days=randint(5, 15))])
@classmethod
def tearDownClass(cls):
print('teardown CoursesTestCase')
super().tearDownClass()
def test_courses_url_accessible(self): def test_courses_url_accessible(self):
print('get ', reverse('courses')) print('get ', reverse('courses'))
resp = self.client.get(reverse('courses')) resp = self.client.get(reverse('courses'))
@ -35,16 +45,79 @@ class CoursesTestCase(TestCase):
print('get ', reverse('course_edit', args=[course.id])) print('get ', reverse('course_edit', args=[course.id]))
resp = self.client.get(reverse('course_edit', args=[course.id])) resp = self.client.get(reverse('course_edit', args=[course.id]))
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
'''
class CourseEditTestCase(TestCase): class CourseEditTestCase(SeleniumTestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.driver = webdriver.Chrome()
create_admin() create_admin()
create_batch_unique(CourseFactory, status=Course.STATUS_CHOICES[:3], price=[0, 1000], UserFactory.create_batch(5, role=User.AUTHOR_ROLE)
age=Course.AGE_CHOICES[:2], deferred_start_at=[None, timezone.now() + timedelta(days=randint(5, 15))]) # create_batch_unique(CourseFactory, status=Course.STATUS_CHOICES[:3], price=[0, 1000],
# age=Course.AGE_CHOICES[:2], deferred_start_at=[None, timezone.now() + timedelta(days=randint(5, 15))])
@classmethod
def tearDownClass(cls):
print('teardown CourseEditTestCase')
super().tearDownClass()
def test_course_edit(self): def test_course_edit(self):
print('Course.objects.all().count()', Course.objects.all().count()) print('go to google')
self.driver.get('http://www.google.com')
return
user = User.objects.filter(role=User.AUTHOR_ROLE).first()
self.client.force_login(user)
url = self.get_url(reverse('course_create'))
print('url', url)
self.driver.get(url)
# visible always elements
title_el = self.wait_elem_name('course-title')
slug_el = self.wait_elem_name('course-slug')
category_el = self.wait_elem_name('course-category')
is_paid_no_el = self.wait_elem_name('course-is-paid-no')
is_paid_yes_el = self.wait_elem_name('course-is-paid-yes')
is_paid_input_el = self.wait_elem_name('course-is-paid-input')
age_el = self.wait_elem_name('course-age')
is_featured_el = self.wait_elem_name('course-is-featured')
is_deferred_no_el = self.wait_elem_name('course-is-deferred-no')
is_deferred_yes_el = self.wait_elem_name('course-is-deferred-yes')
is_deferred_input_el = self.wait_elem_name('course-is-deferred-input')
content_btn_el = self.wait_elem_name('course-content-btn')
lessons_btn_el = self.wait_elem_name('course-lessons-btn')
content_el = self.wait_elem_name('course-content')
title = Faker('sentence', nb_words=6)
title_el.send_keys(title)
slug = slugify(unidecode(title[:90]))
print('title', title, 'slug', slug)
self.assertEqual(slug_el.text, slug)
print("is_paid_input_el.get_attribute('checked')", is_paid_input_el.get_attribute('checked'))
self.assertFalse(is_paid_input_el.get_attribute('checked'))
self.assertRaises(callable=lambda: self.driver.find_element_by_name('course-price'))
self.assertRaises(callable=lambda: self.driver.find_element_by_name('course-old-price'))
is_paid_yes_el.click()
try:
price_el = self.wait_elem_name('course-price')
old_price_el = self.wait_elem_name('course-old-price')
except:
self.fail('Price and old price elements not shown')
self.assertFalse(is_deferred_input_el.get_attribute('checked'))
self.assertRaises(callable=lambda: self.driver.find_element_by_name('course-date'))
self.assertRaises(callable=lambda: self.driver.find_element_by_name('course-time'))
is_deferred_yes_el.click()
try:
date_el = self.wait_elem_name('course-date')
time_el = self.wait_elem_name('course-time')
except:
self.fail('Date and time elements not shown')
# lessons_el = self.wait_elem_name('course-lessons')
# add_lesson_el = self.wait_elem_name('course-add-lesson')
# lesson_edit_el = self.wait_elem_name('course-lesson-edit')
# stream_el = self.wait_elem_name('course-stream')

@ -0,0 +1,44 @@
from django.test import TestCase
from selenium import webdriver
from selenium.webdriver.common.by import By
from pyvirtualdisplay import Display
from project.utils.selenium_utils import SeleniumExtensions as SE
from django.conf import settings
class SeleniumTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.display = Display(visible=0, size=(1280, 1024))
cls.display.start()
cls.driver = webdriver.Chrome()
@classmethod
def tearDownClass(cls):
cls.driver.close()
cls.display.stop()
super().tearDownClass()
def get_url(self, url):
return 'http://%s%s' % (settings.MAIN_HOST, url)
def wait_elem_xpath(self, xpath, inside_el=None, wait_time=10):
return SE.wait_elem(self.driver, (By.XPATH, xpath), inside_el, wait_time)
def wait_elems_xpath(self, xpath, inside_el=None, wait_time=10):
return SE.wait_elems(self.driver, (By.XPATH, xpath), inside_el, wait_time)
def wait_elem_css(self, selector, inside_el=None, wait_time=10):
return SE.wait_elem(self.driver, (By.CSS_SELECTOR, selector), inside_el, wait_time)
def wait_elems_css(self, selector, inside_el=None, wait_time=10):
return SE.wait_elems(self.driver, (By.CSS_SELECTOR, selector), inside_el, wait_time)
def wait_elem_name(self, name, inside_el=None, wait_time=10):
return SE.wait_elem(self.driver, (By.NAME, name), inside_el, wait_time)
def wait_elems_name(self, name, inside_el=None, wait_time=10):
return SE.wait_elems(self.driver, (By.NAME, name), inside_el, wait_time)

@ -1,50 +1,46 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
class elem_in(object): class ElementIn(object):
default_find_fn = 'find_element_by_xpath'
def __init__(self, element, xpath, find_fn=None): default_find_fn = 'find_element'
self.element = element
self.xpath = xpath def __init__(self, element, locator, find_fn=None):
self.find_fn = self.default_find_fn if find_fn is None else find_fn self.element = element
self.locator = locator
def __call__(self, driver): self.find_fn = self.default_find_fn if find_fn is None else find_fn
try:
return getattr(self.element, self.find_fn)(self.xpath) def __call__(self, driver):
except: try:
return False return getattr(self.element, self.find_fn)(self.locator)
except:
class elems_in(elem_in): return False
default_find_fn = 'find_elements_by_xpath'
class ElementsIn(ElementIn):
class SeleniumExtensions(object):
default_find_fn = 'find_elements'
@classmethod
def wait_elem(cls, driver, xpath, inside_el=None, wait_time=10):
if inside_el: class SeleniumExtensions(object):
return WebDriverWait(driver, wait_time).until(elem_in(inside_el, xpath))
if xpath[:2] != '//': @classmethod
raise Exception('XPath in wait_elem must start with //') def wait_elem(cls, driver, locator, inside_el=None, wait_time=10):
return WebDriverWait(driver, wait_time).until( if inside_el:
EC.presence_of_element_located( return WebDriverWait(driver, wait_time).until(ElementIn(inside_el, locator))
(By.XPATH, xpath) return WebDriverWait(driver, wait_time).until(
) EC.presence_of_element_located(locator)
) )
@classmethod @classmethod
def wait_elems(cls, driver, xpath, inside_el=None, wait_time=10): def wait_elems(cls, driver, locator, inside_el=None, wait_time=10):
if inside_el: if inside_el:
return WebDriverWait(driver, wait_time).until(elems_in(inside_el, xpath)) return WebDriverWait(driver, wait_time).until(ElementsIn(inside_el, locator))
if xpath[:2] != '//': return WebDriverWait(driver, wait_time).until(
raise Exception('XPath in wait_elems must start with //') EC.presence_of_all_elements_located(locator)
return WebDriverWait(driver, wait_time).until( )
EC.presence_of_all_elements_located(
(By.XPATH, xpath)
)
)

@ -38,3 +38,5 @@ drf_dynamic_fields
flower==0.9.2 flower==0.9.2
unidecode unidecode
factory-boy==2.11.1 factory-boy==2.11.1
pyvirtualdisplay==0.2.1 # + sudo apt-get install xvfb + sudo apt-get install chromium-chromedriver
# + sudo ln -s /usr/lib/chromium-browser/chromedriver /usr/bin/chromedriver

@ -6,12 +6,13 @@
<div class="info__section"> <div class="info__section">
<div class="courses__item"> <div class="courses__item">
<div class="courses__preview"> <div class="courses__preview">
<img class="courses__pic" :src="course.coverImage || defaultCover" width="300px" /> <img class="courses__pic" name="course-cover" :src="course.coverImage || defaultCover" width="300px" />
<div class="upload" v-if="! course.coverImage"> <div class="upload" v-if="! course.coverImage">
<div class="upload__title">Загрузить превью</div> <div class="upload__title">Загрузить превью</div>
<input type="file" class="upload__file" @change="onCoverImageSelected"> <input type="file" class="upload__file" name="course-cover-upload" @change="onCoverImageSelected">
</div> </div>
<a href="#" title="Удалить превью" class="course-delete-cover" v-if="course.coverImage" @click="removeCover"> <a href="#" title="Удалить превью" class="course-delete-cover" v-if="course.coverImage" @click="removeCover"
name="course-cover-delete">
<svg class="icon icon-delete"> <svg class="icon icon-delete">
<use xlink:href="/static/img/sprite.svg#icon-delete"></use> <use xlink:href="/static/img/sprite.svg#icon-delete"></use>
</svg> </svg>
@ -20,13 +21,13 @@
<div class="courses__details"> <div class="courses__details">
<div class="field-category courses__theme theme field info__field--light" v-if="!live" v-bind:class="{ error: (!$v.live && $v.course.category.$dirty || showErrors) && $v.course.category.$invalid }"> <div class="field-category courses__theme theme field info__field--light" v-if="!live" v-bind:class="{ error: (!$v.live && $v.course.category.$dirty || showErrors) && $v.course.category.$invalid }">
<lil-select :value.sync="course.category" :options="categoryOptions" <lil-select :value.sync="course.category" :options="categoryOptions"
placeholder="Выберите категорию"/> placeholder="Выберите категорию" name="course-category"/>
</div> </div>
<div class="courses__old-price" v-if="course.is_paid && course.old_price"><s>{{ course.old_price }}</s></div> <div class="courses__old-price" v-if="course.is_paid && course.old_price"><s>{{ course.old_price }}</s></div>
<div class="courses__price" v-if="course.is_paid && course.price">{{ course.price }}</div> <div class="courses__price" v-if="course.is_paid && course.price">{{ course.price }}</div>
</div> </div>
<div class="courses__title field field" v-bind:class="{ error: ($v.course.title.$dirty || showErrors) && $v.course.title.$invalid }"> <div class="courses__title field field" v-bind:class="{ error: ($v.course.title.$dirty || showErrors) && $v.course.title.$invalid }">
<textarea class="field__textarea" <textarea class="field__textarea" name="course-title"
rows="1" rows="1"
:title="titles.courseTitle" :title="titles.courseTitle"
v-autosize="course.title" v-autosize="course.title"
@ -34,7 +35,7 @@
v-model="course.title" v-model="course.title"
placeholder="Добавить заголовок"></textarea> placeholder="Добавить заголовок"></textarea>
</div> </div>
<div class="courses__content field" style="width: 300px;" <div class="courses__content field" style="width: 300px;" name="course-short-description"
v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }"> v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }">
<vue-redactor :value.sync="course.short_description" placeholder="Добавить краткое описание"/> <vue-redactor :value.sync="course.short_description" placeholder="Добавить краткое описание"/>
</div> </div>
@ -46,7 +47,7 @@
<div v-if="!live" class="info__field field"> <div v-if="!live" class="info__field field">
<div class="field__label field__label_gray">ССЫЛКА</div> <div class="field__label field__label_gray">ССЫЛКА</div>
<div class="field__wrap"> <div class="field__wrap">
<input type="text" class="field__input" v-model="course.slug" @input="slugChanged = true"> <input type="text" class="field__input" name="course-slug" v-model="course.slug" @input="slugChanged = true">
</div> </div>
<div class="field__wrap field__wrap--additional">{{ courseFullUrl }}</div> <div class="field__wrap field__wrap--additional">{{ courseFullUrl }}</div>
</div> </div>
@ -55,7 +56,7 @@
v-bind:class="{ error: ($v.course.stream.$dirty || showErrors) && $v.course.stream.$invalid }"> v-bind:class="{ error: ($v.course.stream.$dirty || showErrors) && $v.course.stream.$invalid }">
<div class="field__label field__label_gray">ССЫЛКА НА VIMEO</div> <div class="field__label field__label_gray">ССЫЛКА НА VIMEO</div>
<div class="field__wrap"> <div class="field__wrap">
<input type="text" class="field__input" v-model="course.stream"> <input type="text" name="course-stream" class="field__input" v-model="course.stream">
</div> </div>
</div> </div>
@ -71,8 +72,8 @@
v-bind:class="{ error: ($v.course.duration.$dirty || showErrors) && $v.course.duration.$invalid }"> v-bind:class="{ error: ($v.course.duration.$dirty || showErrors) && $v.course.duration.$invalid }">
<div class="field__label field__label_gray">ПРОДОЛЖИТЕЛЬНОСТЬ</div> <div class="field__label field__label_gray">ПРОДОЛЖИТЕЛЬНОСТЬ</div>
<div class="field__wrap field__wrap__appended"> <div class="field__wrap field__wrap__appended">
<input type="text" class="field__input field__input__appended" v-model.number="course.duration" <input type="text" class="field__input field__input__appended" name="course-duration"
@input="$v.course.duration.$touch()"> v-model.number="course.duration" @input="$v.course.duration.$touch()">
<button disabled class="field__append">{{pluralize(course.duration, ['день', 'дня', 'дней'])}}</button> <button disabled class="field__append">{{pluralize(course.duration, ['день', 'дня', 'дней'])}}</button>
</div> </div>
</div> </div>
@ -81,12 +82,14 @@
<div class="field__label field__label_gray">ДОСТУП</div> <div class="field__label field__label_gray">ДОСТУП</div>
<div class="field__wrap"> <div class="field__wrap">
<label class="field__switch switch switch_lg switch_circle"> <label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="false" class="switch__input" v-model="course.is_paid"> <input type="radio" :value="false" class="switch__input" v-model="course.is_paid"
<span class="switch__content">Бесплатный</span> name="course-is-paid-input">
<span class="switch__content" name="course-is-paid-no">Бесплатный</span>
</label> </label>
<label class="field__switch switch switch_lg switch_circle"> <label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="true" class="switch__input" v-model="course.is_paid"> <input type="radio" :value="true" class="switch__input" v-model="course.is_paid"
<span class="switch__content">Платный</span> name="course-is-paid-input">
<span class="switch__content" name="course-is-paid-yes">Платный</span>
</label> </label>
</div> </div>
</div> </div>
@ -94,27 +97,29 @@
<div v-if="course.is_paid" class="info__field field"> <div v-if="course.is_paid" class="info__field field">
<div class="field__label field__label_gray">СТОИМОСТЬ</div> <div class="field__label field__label_gray">СТОИМОСТЬ</div>
<div class="field__wrap field__wrap__appended field__wrap__100px"> <div class="field__wrap field__wrap__appended field__wrap__100px">
<input type="text" class="field__input field__input__appended" v-model.number.lazy="displayPrice"> <input type="text" class="field__input field__input__appended" v-model.number.lazy="displayPrice"
name="course-price">
<button disabled class="field__append">руб.</button> <button disabled class="field__append">руб.</button>
</div> </div>
</div> </div>
<div v-if="course.is_paid" class="info__field field"> <div v-if="course.is_paid" class="info__field field">
<div class="field__label field__label_gray">СТОИМОСТЬ БЕЗ СКИДКИ</div> <div class="field__label field__label_gray">СТОИМОСТЬ БЕЗ СКИДКИ</div>
<div class="field__wrap field__wrap__appended field__wrap__100px"> <div class="field__wrap field__wrap__appended field__wrap__100px">
<input type="text" class="field__input field__input__appended" v-model.number.lazy="displayOldPrice"> <input type="text" class="field__input field__input__appended" v-model.number.lazy="displayOldPrice"
name="course-old-price">
<button disabled class="field__append">руб.</button> <button disabled class="field__append">руб.</button>
</div> </div>
</div> </div>
<div v-if="!live" class="info__field field"> <div v-if="!live" class="info__field field">
<div class="field__label field__label_gray">ВОЗРАСТ</div> <div class="field__label field__label_gray">ВОЗРАСТ</div>
<div class="field__wrap"> <div class="field__wrap">
<lil-select :value.sync="course.age" :options="ages" value-key="value" <lil-select :value.sync="course.age" :options="ages" value-key="value" name="course-age"
placeholder="Выберите возраст"/> placeholder="Выберите возраст"/>
</div> </div>
</div> </div>
<label v-if="me && !live && me.role === ROLE_ADMIN" class="info__switch switch switch_lg"> <label v-if="me && !live && me.role === ROLE_ADMIN" class="info__switch switch switch_lg">
<input type="checkbox" class="switch__input" v-model="course.is_featured"> <input type="checkbox" class="switch__input" v-model="course.is_featured">
<span class="switch__content">Выделить</span> <span class="switch__content" name="course-is-featured">Выделить</span>
</label> </label>
</div> </div>
<div v-if="!live" class="info__fieldset"> <div v-if="!live" class="info__fieldset">
@ -122,12 +127,14 @@
<div class="field__label field__label_gray">ЗАПУСК</div> <div class="field__label field__label_gray">ЗАПУСК</div>
<div class="field__wrap"> <div class="field__wrap">
<label class="field__switch switch switch_lg switch_circle"> <label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="false" class="switch__input" v-model="course.is_deferred"> <input type="radio" :value="false" class="switch__input" v-model="course.is_deferred"
<span class="switch__content">Мгновенный</span> name="course-is-deferred-input">
<span class="switch__content" name="course-is-deferred-no">Мгновенный</span>
</label> </label>
<label class="field__switch switch switch_lg switch_circle"> <label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="true" class="switch__input" v-model="course.is_deferred"> <input type="radio" :value="true" class="switch__input" v-model="course.is_deferred"
<span class="switch__content">Отложенный</span> name="course-is-deferred-input">
<span class="switch__content" name="course-is-deferred-yes">Отложенный</span>
</label> </label>
</div> </div>
</div> </div>
@ -135,13 +142,15 @@
<div class="info__field field"> <div class="info__field field">
<div class="field__label">ДАТА</div> <div class="field__label">ДАТА</div>
<div class="field__wrap"> <div class="field__wrap">
<vue-datepicker :disabled="disabledDates" input-class="field__input" v-model="course.date" language="ru" format="dd/MM/yyyy"/> <vue-datepicker :disabled="disabledDates" input-class="field__input" name="course-date"
v-model="course.date" language="ru" format="dd/MM/yyyy"/>
</div> </div>
</div> </div>
<div class="field-time info__field field"> <div class="field-time info__field field">
<div class="field__label">ВРЕМЯ</div> <div class="field__label">ВРЕМЯ</div>
<div class="field__wrap"> <div class="field__wrap">
<lil-select :value.sync="course.time" value-key="value" :options="timeOptions" placeholder="Выберите время"/> <lil-select :value.sync="course.time" value-key="value" :options="timeOptions" name="course-time"
placeholder="Выберите время"/>
</div> </div>
</div> </div>
</div> </div>
@ -153,12 +162,12 @@
<div class="section__center center"> <div class="section__center center">
<div class="kit" style="margin: 0 auto;"> <div class="kit" style="margin: 0 auto;">
<div v-if="!live" id="course-redactor__nav" class="kit__nav"> <div v-if="!live" id="course-redactor__nav" class="kit__nav">
<button class="kit__btn btn btn_lg" <button class="kit__btn btn btn_lg" name="course-content-btn"
v-bind:class="{ 'btn_stroke': viewSection === 'course', 'btn_gray': viewSection !== 'course' }" v-bind:class="{ 'btn_stroke': viewSection === 'course', 'btn_gray': viewSection !== 'course' }"
type="button" @click="showCourse">Описание type="button" @click="showCourse">Описание
курса курса
</button> </button>
<button class="kit__btn btn btn_lg" <button class="kit__btn btn btn_lg" name="course-lessons-btn"
v-bind:class="{ 'btn_stroke': viewSection === 'lessons', 'btn_gray': viewSection !== 'lessons' }" v-bind:class="{ 'btn_stroke': viewSection === 'lessons', 'btn_gray': viewSection !== 'lessons' }"
type="button" type="button"
@click="showLessons" @click="showLessons"
@ -167,7 +176,7 @@
</button> </button>
</div> </div>
<div v-if="viewSection === 'course'" class="kit__body"> <div v-if="viewSection === 'course'" class="kit__body">
<block-content :content.sync="course.content"></block-content> <block-content :content.sync="course.content" name="course-content"></block-content>
<!--<div class="kit__foot"> <!--<div class="kit__foot">
<button type="submit" class="kit__submit btn btn_md" v-bind:class="{ loading: courseSaving }"> <button type="submit" class="kit__submit btn btn_md" v-bind:class="{ loading: courseSaving }">
@ -178,7 +187,8 @@
<div v-if="viewSection === 'lessons'" class="kit__body"> <div v-if="viewSection === 'lessons'" class="kit__body">
<div class="lessons__title title">Содержание курса</div> <div class="lessons__title title">Содержание курса</div>
<div v-if="!lessonsLoading" class="lessons__list"> <div v-if="!lessonsLoading" class="lessons__list">
<vue-draggable v-model="lessons" @start="drag=true" @end="onLessonsChanged" :options="{ handle: '.sortable__handle' }"> <vue-draggable v-model="lessons" @start="drag=true" @end="onLessonsChanged" name="course-lessons"
:options="{ handle: '.sortable__handle' }">
<div class="lessons__item" v-for="(lesson, index) in lessons" :key="lesson.id"> <div class="lessons__item" v-for="(lesson, index) in lessons" :key="lesson.id">
<div class="lessons__actions"> <div class="lessons__actions">
<button class="sortable__handle" type="button"> <button class="sortable__handle" type="button">
@ -206,7 +216,8 @@
</div> </div>
<div v-if="lessonsLoading">Загрузка...</div> <div v-if="lessonsLoading">Загрузка...</div>
<div class="lessons__foot"> <div class="lessons__foot">
<button type="button" class="lessons__btn btn btn_md" @click="addLesson">СОЗДАТЬ УРОК</button> <button type="button" class="lessons__btn btn btn_md" @click="addLesson"
name="course-add-lesson">СОЗДАТЬ УРОК</button>
</div> </div>
</div> </div>
</div> </div>
@ -215,7 +226,7 @@
</form> </form>
<form v-if="viewSection === 'lessons-edit'" @submit.prevent="onLessonSubmit"> <form v-if="viewSection === 'lessons-edit'" @submit.prevent="onLessonSubmit">
<lesson-redactor :$v="$v" :lesson.sync="currentLesson" :saving.sync="lessonSaving" :access-token="accessToken" <lesson-redactor :$v="$v" :lesson.sync="currentLesson" :saving.sync="lessonSaving" :access-token="accessToken"
v-on:back="goToLessons" /> v-on:back="goToLessons" name="course-lesson-edit" />
</form> </form>
</div> </div>
<div v-else> <div v-else>

Loading…
Cancel
Save